写让别人能读懂的代码

随着软件行业的不断发展,历史遗留的程序越来越多,代码的维护成本越来越大,甚至大于开发成本。而新功能的开发又常常依赖于旧代码,阅读旧代码所花费的时间几乎要大于写新功能的代码。

我前几天看了一本书,书中有这么一句话:

“复杂的代码往往都是新手所写,只有经验老道的高手才能写出简单,富有表现力的代码”

此话虽然说的有点夸张,可是也说明了经验的重要性。

我们所写的代码除了让机器执行外,还需要别人来阅读。所以我们要写:

  1. 让别人能读懂的代码

  2. 可扩展的代码

  3. 可测试的代码(代码应该具备可测试性,对没有可测试性的代码写测试,是浪费生命的表现)

其中2,3点更多强调的是面向对象的设计原则。而本文则更多关注于局部的代码问题,本文通过举例的方式,总结平时常犯的错误和优化方式。

本文的例子基于两个指导原则:

一.DRY(Don't repeat yourself)

此原则如此重要,简单来说是因为:

  • 代码越少,Bug也越少

  • 没有重复逻辑的代码更易于维护,当你修复了一个bug,如果相同的逻辑还出现在另外一个地方,而你没意识到,你有没有觉得自己很冤?

二.TED原则

  • 简洁(Terse)

  • 具有表达力(Expressive)

  • 只做一件事(Do one thing)

三.举例说明

1.拒绝注释,用代码来阐述注释

反例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// !@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*((
/// </summary>
/// <returns></returns>
public decimal GetCash()
{
//!@#$%^&^&*((!@#$%^&^&*((
vara = new List<decimal>() { 2m, 3m, 10m };
varb = 2;
varc = 0m;
//!@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*((
foreach(var in a)
{
c += p*b;
}
returnc;
}

重构后:

1
2
3
4
5
6
public decimal CalculateTotalCash()
{
var prices=new List<decimal>(){2m,3m,10m};
var itemCount = 2;
return prices.Sum(p => p*itemCount);
}

良好的代码命名完全可以替代注释的作用,如果你正在试图写一段注释,从某种角度来看,你正在试图写一段别人无法理解的代码。

当你无法为你的方法起一个准确的名称时,很可能你的方法不止做了一件事,违反了(Do one thing)。特别是你想在方法名中加入:And,Or,If等词时

2. 为布尔变量赋值

反例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public bool IsAdult(int age)
{
bool isAdult;
if(age > 18)
{
isAdult = true;
}
else
{
isAdult = false;
}
return isAdult;
}

重构后:

1
2
3
4
5
public bool IsAdult(int age)
{
var isAdult = age > 18;
return isAdult;
}

3.双重否定的条件判断

反例:

1
2
3
4
if(!isNotRemeberMe)
{
}

重构后:

1
2
3
4
if(isRemeberMe)
{
}

不管你有没有见过这样的条件,反正我见过。见到这样的条件判断,我顿时就晕了。

4.拒绝HardCode,拒绝挖坑

反例:

1
2
3
4
if(carName == "Nissan")
{
}

重构后:

1
2
3
4
if(car == Car.Nissan)
{
}

既然咱们玩的是强类型语言,咱就用上编译器的功能,让错误发生在编译阶段

5.拒绝魔数,拒绝挖坑

反例:

1
2
3
4
if(age > 18)
{
}

重构后:

1
2
3
4
5
constintadultAge = 18;
if(age > adultAge)
{
}

所谓魔数(Magic number)就是一个魔法数字,读者完全弄不明白你这个数字是什么,这样的代码平时见的多了

6.复杂的条件判断

反例:

1
2
3
4
5
6
7
if(job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| job.JobTitle.IsNullOrWhiteSpace())
{
//....
}

重构后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(CanBeDeleted(job))
{
//
}
privateboolCanBeDeleted(Job job)
{
var invalidJobState = job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired;
va rinvalidJob = string.IsNullOrEmpty(job.JobTitle);
return invalidJobState || invalidJob;
}

有没有豁然开朗的赶脚?

7.嵌套判断

反例:

1
2
3
4
5
6
7
8
9
10
11
12
varisValid = false;
if(!string.IsNullOrEmpty(user.UserName))
{
if(!string.IsNullOrEmpty(user.Password))
{
if(!string.IsNullOrEmpty(user.Email))
{
isValid = true;
}
}
}
return isValid;

重构后:

1
2
3
4
if(string.IsNullOrEmpty(user.UserName)) return false;
if(string.IsNullOrEmpty(user.Password)) return false;
if(string.IsNullOrEmpty(user.Email)) return false;
return true;

第一种代码是受到早期的某些思想:使用一个变量来存储返回结果。事实证明,你一旦知道了结果就应该尽早返回。

8.使用前置条件

反例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(!string.IsNullOrEmpty(userName))
{
if(!string.IsNullOrEmpty(password))
{
//register
}
else
{
thrownewArgumentException("user password can not be empty");
}
}
else
{
thrownewArgumentException("user name can not be empty");
}

重构后:

1
2
3
if(string.IsNullOrEmpty(userName)) thrownewArgumentException("user name can not be empty");
if(string.IsNullOrEmpty(password)) thrownewArgumentException("user password can not be empty");
//register

重构后的风格更接近契约编程,首先要满足前置条件,否则免谈。

9.参数过多,超过3个

反例:

1
2
3
4
public void RegisterUser(stringuserName, stringpassword, stringemail, stringphone)
{
}

重构后:

1
2
3
4
public void RegisterUser(User user)
{
}

过多的参数让读者难以抓住代码的意图,同时过多的参数将会影响方法的稳定性。另外也预示着参数应该聚合为一个Model

10.方法签名中含有布尔参数

反例:

1
2
3
4
public void RegisterUser(User user, bool sendEmail)
{
}

重构后:

1
2
3
4
5
6
7
8
9
public void RegisterUser(User user)
{
}
public void SendEmail(User user)
{
}

布尔参数在告诉方法不止做一件事,违反了Do one thing

10.写具有表达力的代码

反例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private string CombineTechnicalBookNameOfAuthor(List<Book> books, string author)
{
var filterBooks = newList<Book>();
foreach(va rbook in books)
{
if(book.Category == BookCategory.Technical && book.Author == author)
{
filterBooks.Add(book);
}
}
varname = "";
foreach(var book in filterBooks)
{
name += book.Name + "|";
}
returnname;
}

重构后:

1
2
3
4
5
6
7
8
9
private string CombineTechnicalBookNameOfAuthor(List<Book> books, string author)
{
varcombinedName = books.Where(b => b.Category == BookCategory.Technical)
.Where(b => b.Author == author)
.Select(b => b.Name)
.Aggregate((a, b) => a + "|"+ b);
return combinedName;
}

相对于命令式代码,声明性代码更加具有表达力,也更简洁。这也是函数式编程为什么越来越火的原因之一。

四.关于DRY

平时大家重构代码,一个重要的思想就是DRY。我要分享一个DRY的反例:

项目在架构过程中会有各种各样的MODEL层,例如:DomainModel,ViewModel,DTO。很多时候这几个Model里的字段大部分是相同的,于是有人就会想到DRY原则,干脆直接用一种类型,省得粘贴复制,来回转换。

这个反例失败的根本原因在于:这几种Model职责各不相同,虽然大部分情况下内容会有重复,但是他们担当着各种不同的角色。

考虑这种场景: DomainModel有一个字段DateTime Birthday{get;set;},ViewModel同样具有DateTime Birthday{get;set;}。需求升级:要求界面不再显示生日,只需要显示是否成年。我们只需要在ViewModel中添加一个Bool IsAdult{get{return ....}}即可,DomainModel完全不用变化。

五.利用先进的生产工具

以vs插件中的Reshaper为例,本文列举的大部分反例,Reshaprer均能给予不同程度的提示。经过一段时间的练习,当Reshaper对你的代码给予不了任何提示的时候,你的代码会有一个明显的提高。

截图说明Reshaper的提示功能:


光标移动在波浪线处,然后Alt+Enter,Resharper 会自动对代码进行优化

如果你能够避免本文总结的反例,你的代码就已经具备了优秀代码应有的基因。当然高质量的代码还需要良好的设计和遵循面向对象编程的原则。 如果想了解更多相关内容,请阅读《代码大全》,《代码整洁之道》,《重构 改善既有代码的设计》,《敏捷软件开发 原则、模式与实践》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

非阻IO与EWOULDBLOCK EAGAIN

非阻塞读写 默认 socket 是阻塞的&#xff0c;读写函数 read, readv, recv, recvfrom, recvmsg 以及 write, writev, send, sendto, sendmsg 都有可能会阻塞。可以将 socket 描述字设为非阻塞&#xff0c;这样&#xff0c;当 socket 描述字未就绪时&#xff0c;调用以上读写函…

一起学windows phone7开发(二十一.二 Map控件的简单使用)

1. 注册地图&#xff1a; 在使用地图之前必须先申请register key https://www.bingmapsportal.com/ 将申请到的key填到这个属性&#xff0c;地图才可以正常使用。 CredentialsProvider 属性&#xff1a;填写申请到的Register key。 2.设置中心点&#xff1a; <my:Map Height…

UML类图五种关系与代码的对应关系

UML类图中的五种关系的耦合强弱比较&#xff1a;依赖<关联<聚合<组合<继承 一、依赖关系&#xff1a; &#xff08;一&#xff09;说明 虚线箭头 可描述为&#xff1a;Uses a 依赖是类的五种关系中耦合最小的一种关系。 因为在生成代码的时候&#xff0c;这两个关系…

使用 rapidxml 做配置文件

对于配置文件&#xff0c;一般会选用ini,xml 等等的配置格式。如何快速高效的从文件内读取自己想要的信息是每个做配置文件想要达到的效果。对以小型开发我们并不用时用到msxml这种重量级的解析器。那样会给自己添麻烦的。这里我推荐大家使用rapidxml。 之前使用tinyxml 感觉还…

水晶报表实现(一)

WINFORM下创建水晶报表&#xff1a; 1、新建一个“windows应用程序” 2、添加一个数据集&#xff08;.xsd&#xff09;文件&#xff0c;它是ADO.NET数据集&#xff0c;数据集用于在断开缓存中存储数据&#xff0c;它的结构类似于关系数据库的接口&#xff0c;它公开表、行和列的…

Java swing 实现下拉框和文本框同步显示

想要MyEclipse中的swing中实现下拉框和文本框实现&#xff0c;对下拉框创建MouseEvent、ItemEvent、ActionEvent private void xingbieMouseClicked(java.awt.event.MouseEvent evt) { // TODO add your handling code here: setSelectedItem(evt, this.xingbie1); } private v…

python image 转成字节_就是这么流弊!三行Python代码,让数据处理速度提高2到6倍

选自TowardsDataScience作者&#xff1a;George Seif本文转自机器之心(nearhuman2014)本文可以教你仅使用 3 行代码&#xff0c;大大加快数据预处理的速度。Python 是机器学习领域内的首选编程语言&#xff0c;它易于使用&#xff0c;也有很多出色的库来帮助你更快处理数据。但…

LSGO软件技术团队内部技术交流【2015-2016(1)第七周】

LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…

python beautiful soup 标签完全相同_Python爬取Python教程并制作成pdf

欢迎点击右上角关注小编&#xff0c;除了分享技术文章之外还有很多福利&#xff0c;私信学习资料可以领取包括不限于Python实战演练、PDF电子文档、面试集锦、学习资料等。想要把教程变成PDF有三步&#xff1a;1、先生成空html&#xff0c;爬取每一篇教程放进一个新生成的div&a…

LSGO软件技术团队2015~2016学年第八周(1019~1025)总结

LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…

ENVI计算公式(一)

<1>大于1的值赋予1&#xff0c;小于0的值赋予0 ((b1 lt 0)*(0)(b1 ge 0)*b1)or((b1 gt 1)*(1)(b1 ge 0)*b1) <2>modis数据计算ndvi b1*0.0001 <3>modis数据计算地表温度&#xff08;单位&#xff1a;摄氏度&#xff09; b1*0.02-273.15 <4>modis数据…

list取数据_Day.5利用Pandas做数据处理(二)

数据合并使用Join()合并&#xff0c;合并的方式是根据行和行进行合并。# 使用join合并&#xff0c;着重关注的是 行的合并import pandas as pd df1pd.DataFrame({Red:[1,3,5],Green:[5,0,3]},indexlist(abc))df2pd.DataFrame({Blue:[1,9,8],Yellow:[6,6,7]},indexlist(cde))pri…

MRP的数据处理-华北水利水电大学(作业)

这是之前学经济方向的同学让我利用C语言写的关于MRP的数据处理的过程&#xff0c;在用C语言写的过程中利用了动态数组使得时区不仅限于这8时区&#xff0c;有相关的同学可以进行查看 代码运行结果如下&#xff08;该代码可以自动调整参数&#xff0c;时区可以设置8天以上&#…

LSGO软件技术团队2015~2016学年第九周(1026~1101)总结

简述&#xff1a; LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养…

git 每次都要输入用户名密码_Git向GitHub提供代码

一.前期准备工作首先建立一个GitHub账号&#xff0c;这个账号和密码邮箱要记住&#xff0c;如果忘记了也可以找回&#xff0c;会麻烦一些。在官网下载一个Git,可以自己根据默认进行安装&#xff0c;这样也是没有问题的&#xff0c;如果系统盘的空间不够大&#xff0c;可以安装到…

C# Socket 入门5 UPD 结构体 与 C++ 通信

1. 同样&#xff0c; 我们先看看这一个比简单的 结构体 代码 usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.Runtime.InteropServices;namespaceCSharp_Socket_5{ ///<summary>///通信消息格式 ///</summary>[Serializable] …

坐标点获取并显示

在工作当中经常遇到根据名称获得相应坐标的情况,我们可以利用百度地图api来获取对应地点的坐标。 例如: 目前我们手中有需要获取大学坐标的名称,想要获取其坐标,并将其进行可视化显示。 以下是要查询的大学名称(现实当中可以是几千条数据) 以下是我们查询的结果 …

LSGO软件技术团队2015~2016学年第十周(1102~1108)总结

团队简述&#xff1a; LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校…

matlab中GUI的属性检查器中的XLimMode是什么_如何在Matlab中使用GUI做一个简易音乐播放器? ---- (二)GUIDE...

咕咕怪由于昨天有重要的事情所以咕了一天的文章 &#xff08;感觉写得挺基础的&#xff0c;对各个部分有一定了解的童鞋可以直接跳过了解的部分用Matlab做一个app有几种办法呢&#xff1f;同样的&#xff0c;帮助文档告诉了我们答案&#xff1a;三种。英语好的童鞋看完这张图应…

DDMMSS.SS转为DD

有时候甲方会给我们一些坐标&#xff0c;但是在arcgis中是无法直接显示的 这是因为该格式是DDMMSS.SS的格式&#xff0c;而arcgis支持的是DD格式的&#xff0c;其中dd单位是度&#xff0c;mm单位是分&#xff0c;ss.ss单位是秒 所以要在arcgis中显示我们需要将其转换为DD的格式…