关于程序员:如何提高代码的可读性-读编写可读代码的艺术

2次阅读

共计 14546 个字符,预计需要花费 37 分钟才能阅读完成。

一. 为什么读这本书

很多同行在编写代码的时候往往只关注一些宏观上的主题:架构,设计模式,数据结构等等,却漠视了一些更细节上的点:比方变量如何命名与应用,控制流的设计,以及正文的写法等等。以上这些细节上的货色能够用 代码的可读性 来概括。

不同于宏观上的架构,设计模式等须要好几个类,好几个模块能力看进去:代码的可读性是可能立即从宏观上的,一个变量的命名,函数的逻辑划分,正文的信息品质外面看进去的。

宏观层面上的货色诚然重要,然而代码的可读性也属于评估代码品质的一个无奈让人漠视的指标:它影响了浏览代码的老本(毕竟代码次要是给人看的),甚至会影响代码出错的概率!

这里援用《编写可读代码的艺术》这本书里的一句话:

对于一个整体的软件系统而言,既须要宏观的架构决策,设计与领导准则,也必须器重宏观上的的代码细节。在软件历史中,有许多影响深远的重大失败,其本源往往是编码细节呈现了疏漏。

因而笔者认为代码的可读性能够作为考量一名程序员业余水平的指标。

或者曾经有很多同行也正在努力提高本人代码的可读性。然而这里有一个很典型的错觉(笔者之前就有这种错觉)是:越少的代码越容易让人了解。

然而事实上,并不是代码越精简就越容易让人了解。绝对于谋求最小化代码行数,一个更好的进步可读性办法是 最小化人们了解代码所须要的工夫。

这就引出了这本中的一个外围定理:

可读性根本定理:代码的写法该当使他人了解它所须要的工夫最小化。

这本书讲的就是对于“如何进步代码的可读性”。笔者总结下来,这本书从浅入深,在三个档次通知了咱们如何让代码易于了解:

  • 表层上的改良:在命名办法(变量名,办法名),变量申明,代码格局,正文等方面的改良。
  • 控制流和逻辑的改良:在控制流,逻辑表达式上让代码变得更容易了解。
  • 构造上的改良:长于抽取逻辑,借助自然语言的形容来改善代码。

二. 表层的改良

首先来讲最简略的一层如何改良,波及到以下几点:

  • 如何命名
  • 如何申明与应用变量
  • 如何简化表达式
  • 如何让代码具备美感
  • 如何写正文

如何命名

对于如何命名,作者提出了一个要害思维:

要害思维:把尽可能多的信息装入名字中。

这里的多指的是有价值的多。那么如何做到有价值呢?作者介绍了以下几个倡议:

  • 抉择业余的词汇,防止泛泛的名字
  • 给名字附带更多信息
  • 决定名字最适宜的长度
  • 名字不能引起歧义

抉择业余的词汇,防止泛泛的名字

一个比拟常见的反例:get

get这个词最好是用来做轻量级的取办法的结尾,而如果用到其余的中央就会显得很不业余。

举个书中的例子:

getPage(url)

通过这个办法名很难判断出这个办法是从缓存中获取页面数据还是从网页中获取。如果是从网页中获取,更业余的词应该是 fetchPage(url) 或者downloadPage(url)

还有一个比拟常见的反例:returnValueretval。这两者都是“返回值”的意思,他们被滥用在各个有返回值的函数外面。其实这两个词除了携带他们原本的意思 返回值 以外并不具备任何其余的信息,是典型的泛泛的名字。

那么如何抉择一个业余的词汇呢?答案是在十分贴近你本人的用意的根底上,抉择一个富裕表现力的词汇。

举几个例子:

  • 绝对于 make,抉择create,generate,build 等词汇会更有表现力,更加业余。
  • 绝对于 find,抉择search,extract,recover 等词汇会更有表现力,更加业余。
  • 绝对于retval,抉择一个能充沛形容这个返回值的性质的名字,例如:
var euclidean_norm = function (v){
    var retval = 0.0;
    for (var i = 0; i < v.length; i += 1;)
       retval += v[i] * v[i];
    return Match.sqrt(retval);
}
复制代码

这里的 retval 示意的是“平方的和”,因而 sum_squares 这个词更加贴切你的用意,更加业余。

然而,有些状况下,泛泛的名字也是有意义的,例如一个替换变量的情景:

if (right < left){
    tmp = right;
    right = left;
    left = tmp;
}
复制代码

像下面这种 tmp 只是作为一个长期存储的状况下,tmp 表白的意思就比拟贴切了。因而,像 tmp 这个名字,只实用于短期存在而且个性为临时性的变量。

给名字附带更多信息

除了抉择一个业余,贴切用意的词汇,咱们也能够通过增加一些前后缀来给这个词附带更多的信息。这里所指的更多的信息有三种:

  • 变量的单位
  • 变量的属性
  • 变量的格局

为变量增加单位

有些变量是有单位的,在变量名的前面增加其单位能够让这个变量名携带更多信息:

  • 一个表白工夫距离的变量,它的单位是秒:绝对于 duractionducation_secs 携带了更多的信息
  • 一个表白内存大小的变量,它的单位是 mb:绝对于 sizecache_mb 携带了更多的信息。

为变量增加重要属性

有些变量是具备一些十分重要的属性,其重要水平是不容许使用者疏忽的。例如:

  • 一个 UTF- 8 格局的 html 字节,绝对于 htmlhtml_utf8 更加分明地形容了这个变量的格局。
  • 一个纯文本,须要加密的明码字符串:绝对于 passwordplaintext_password 更分明地形容了这个变量的特点。

为变量抉择适当的格局

对于命名,有些既定的格局须要留神:

  • 应用大驼峰命名来示意类名:HomeViewController
  • 应用小驼峰命名来示意属性名:userNameLabel
  • 应用下划线连接词来示意变量名:product_id
  • 应用 kConstantName 来示意常量:kCacheDuraction
  • 应用 MACRO_NAME 来示意宏:SCREEN_WIDTH

决定名字最适宜的长度

名字越长越难记住,名字越短所持有的信息就越少,如何决定名字的长度呢?这里有几个准则:

  • 如果变量的作用域很小,能够取很短的名字
  • 驼峰命名中的单元不能超过 3 个
  • 不能应用大家不相熟的缩写
  • 丢掉不必要的单元

如果变量的作用域很小,能够取很短的名字

如果一个变量作用域很小:则给它取一个很短的名字也不妨。

看上面这个例子:

if(debug){
    map <string,int>m;
    LookUpNamesNumbers(&m);
    Print(m);
}
复制代码

在这里,变量的类型和应用范畴一眼可见,读者能够理解这段代码的所有信息,所以即便是取 m 这个十分简短的名字,也不影响读者了解作者的用意。

相同的,如果 m 是一个全局变量,当你看到上面这段代码就会很头疼,因为你不明确它的类型:

LookUpNamesNumbers(&m);
Print(m);
复制代码

驼峰命名中的单元不能超过 3 个

咱们晓得驼峰命名能够很清晰地体现变量的含意,然而当驼峰命名中的单元超过了 3 个之后,就会很影响浏览体验:

userFriendsInfoModel

memoryCacheCalculateTool

是不是看上去很吃力?因为咱们大脑同时能够记住的信息十分无限,尤其是在看代码的时候,这种短期记忆的局限性是无奈让咱们同时记住或者霎时了解几个具备 3~4 个单元的变量名的。所以咱们须要在变量名外面去除一些不必要的单元:

丢掉不必要的单元

有些单元在变量外面是能够去掉的,例如:

convertToString能够省略成toString

不能应用大家不相熟的缩写

有些缩写是大家熟知的:

  • doc 能够代替document
  • str 能够代替string

然而如果你想用 BEManager 来代替 BackEndManager 就比拟不适合了。因为不理解的人简直是无奈猜到这个名称的真正意义的。

所以遇到相似这种状况咱们不能偷懒,该是什么就是什么,否则会起到相同的成果。因为它看起来十分生疏,跟咱们熟知的一些缩写规定相去甚远。

名字不能引起歧义

有些名字会引起歧义,例如:

  • filter:过滤这个词,能够是过滤出符合标准的,也能够是缩小不符合标准的:是两种齐全相同的后果,所以不举荐应用。
  • clip:相似的,到底是在原来的根底上截掉某一段还是另外截进去某一段呢?同样也不举荐应用。
  • 布尔值:read_password: 是表白须要读取明码,还是曾经读了明码呢?所以最好应用 need_password 或者 is_authenticated 来代替比拟好。通常来说,给布尔值的变量加上 is,has,can,should 这样的词能够使布尔值表白的意思更加明确

这一节讲了很多对于如何起好一个变量名的办法。其实有一个很简略的准则来判断这个变量名起的是否是好的:那就是:团队的新成员是否能迅速了解这个变量名的含意。如果是,那么这个命名就是胜利的;否则就不要偷懒了,起个好名字,对谁都好。其实如果你养成习惯多花几秒钟想出个好名字,慢慢地,你会发现你的“命名能力”会很快晋升。

如何申明与应用变量

在写程序的过程中咱们会申明很多变量(成员变量,长期变量),而咱们要晓得变量的申明与应用策略是会对代码的可读性造成影响的:

  • 变量越多,越难跟踪它们的动向。
  • 变量的作用域越大,就须要跟踪它们的动向越久。
  • 变量扭转的越频繁,就越难跟踪它的以后值。

绝对的,对于变量的申明与应用,咱们能够从这四个角度来进步代码的可读性:

  1. 缩小变量的个数
  2. 放大变量的作用域
  3. 缩短变量申明与应用其代码的间隔
  4. 变量最好只写一次

缩小变量的个数

在一个函数外面可能会申明很多变量,然而有些变量的申明是毫无意义的,比方:

  • 没有价值的长期变量
  • 示意两头后果的变量

没有价值的长期变量

有些变量的申明齐全是多此一举,它们的存在反而加大了浏览代码的老本:

let now = datetime.datatime.now()
root_message.last_view_time = now    
复制代码

下面这个 now 变量的存在是毫无意义的,因为:

  • 没有拆分任何简单的表达式
  • datetime.datatime.now曾经很分明地表白了意思
  • 只应用了一次,因而而没有压缩任何冗余的代码

所以齐全不必这个变量也是齐全能够的:

root_message.last_view_time = datetime.datatime.now()
复制代码

示意两头后果的变量

有的时候为了达成一个指标,把一件事件分成了两件事件来做,这两件事件两头须要一个变量来传递后果。但往往这件事件不须要分成两件事件来做,这个“两头后果”也就不须要了:

看一个比拟常见的需要,一个把数组中的某个值移除的例子:

var remove_value = function (array, value_to_remove){
    var index_to_remove = null;
    for (var i = 0; i < array.length; i+=1){if (array[i] === value_to_remove){
            index_to_remove = i;
            break;
        }
    }
    if (index_to_remove !== null){array.splice(index_to_remove,1);
    }
} 
复制代码

这外面把这个事件分成了两件事件来做:

  1. 找出要删除的元素的序号,保留在变量 index_to_remove 外面。
  2. 拿到 index_to_remove 当前应用 splice 办法删除它。(这段代码是 JavaScript 代码)

这个例子对于变量的命名还是比拟合格的,但实际上这里所应用的两头后果变量是齐全不须要的,整个过程也不须要分两个步骤进行。来看一下如何一步实现这个需要:

var remove_value = function (array, value_to_remove){for (var i = 0; i < array.length; i+=1){if (array[i] === value_to_remove){array.splice(i,1);
            return;
        }
    }
} 
复制代码

下面的办法外面,当晓得应该删除的元素的序号 i 的时候,就间接用它来删除了应该删除的元素并立刻返回。

除了加重了内存和处理器的累赘(因为不须要开拓新的内容来存储后果变量以及可能不必齐全走遍整个的 for 语句),浏览代码的人也会很快体会代码的用意。

所以在写代码的时候,如果能够“速战速决”,就尽量应用最快,最简洁的形式来实现目标。

放大变量的作用域

变量的作用域越广,就越难追踪它,值也越难管制,所以咱们应该让你的 变量对尽量少的代码可见

比方类的成员变量就相当于一个“小型局部变量”。如果这个类比拟宏大,咱们就会很难追踪它,因为所有办法都能够“隐式”调用它。所以相同地,如果咱们能够把它“降格”为局部变量,就会很容易追踪它的行踪:

// 成员变量,比拟难追踪
class LargeCass{
  string str_;
  
  void Method1(){
     str_ = ...;
     Method2();}
  
  void Method2(){//using str_}
}
复制代码

降格:

// 局部变量,容易追踪
class LargeCass{void Method1(){
     string str = ...;
     Method2(str);
  }
  
  void Method2(string str){//using str}
}
复制代码

所以在设计类的时候如果这个数据(变量)能够通过办法参数来传递,就不要以成员变量来保留它。

缩短变量申明与应用其代码的间隔

在实现一个函数的时候,咱们可能会申明比拟多的变量,但这些变量的应用地位却不都是在函数结尾。

有一个比拟不好的习惯就是无论变量在以后函数的哪个地位应用,都在一开始(函数的结尾)就申明了它们。这样可能导致的问题是:浏览代码的人读到函数后半局部的时候就遗记了这个变量的类型和初始值;而且因为在函数的结尾就申明了好几个变量,也对浏览代码的人的大脑造成了累赘,因为人的短期记忆是无限的,特地是记一些临时还不晓得怎么用的货色。

因而,如果在函数外部须要在不同中央应用几个不同的变量,倡议在真正应用它们之前再申明它。

变量最好只写一次

操作一个变量的中央越多,就越难确定它的以后值。所以在很多语言外面有其各自的形式让一些变量不可变(是个常量),比方 C ++ 里的 const 和 Java 中的final

如何简化表达式

有些表达式比拟长,很难让人马上了解。这时候最好能够将其拆分成更容易的几个小块。能够尝试上面的几个办法:

  • 应用解释变量
  • 应用总结变量
  • 应用德摩根定理

应用解释变量

有些变量会从一个比拟长的算式得出,这个表达式可能很难让人看懂。这时候就须要用一个简短的“解释”变量来诠释算式的含意。应用书中的一个例子:

if line.split(':')[0].strip() == "root"
复制代码

其实下面左侧的表达式其实得出的是用户名,咱们能够用 username 来替换它:

username = line.split(':')[0].strip()
if username == "root"
复制代码

应用总结变量

除了以“变量”替换“算式”,还能够用“变量”来替换含有更多变量更简单的内容,比方条件语句,这时候该变量能够被称为 ” 总结变量 ”。应用书中的一个例子:

if(request.user.id == document.owner_id){//do something}
复制代码

下面这条判断语句所判断的是:“该文档的所有者是不是该用户”。咱们能够应用一个总结性的变量 user_owns_document 来替换它:

final boolean user_owns_document = (request.user.id == document.owner_id);
if (user_owns_document){//do something}
复制代码

应用德摩根定理

德摩根定理:

  1. not(a or b or c)等价于(not a) and (not b) and (not c)
  2. not(a and b and c)等价于(not a) or (not b) or (not c)

当咱们条件语句外面存在内部取反的状况,就能够应用德摩根定理来做个转换。应用书中的一个例子:

// 应用德摩根定理转换以前
if(!(file_exists && !is_protected)){}

// 应用德摩根定理转换当前
if(!file_exists || is_protected){}
复制代码

如何让代码具备美感

在读过一些好的源码之后我有一个感触:好的源码往往都看上去都很漂亮,很有美感。这里说的丑陋和美感不是指代码的逻辑清晰有条理,而是指感官上的视觉感触让人感觉很难受。这是从一种纯正的审美的角度来评估代码的:富裕美感的代码让人赏心悦目,也容易让人读懂。

为了让代码更有美感,采取以下实际会很有帮忙:

  • 用换行和列对齐来让代码更加参差
  • 抉择一个有意义的程序
  • 把代码分成 ” 段落 ”
  • 放弃格调一致性

用换行和列对齐来让代码更加参差

有些时候,咱们能够利用换行和列对齐来让代码显得更加参差。

换行

换行比拟罕用在函数或办法的参数比拟多的时候。

应用换行:

- (void)requestWithUrl:(NSString*)url 
                  method:(NSString*)method 
                params:(NSDictionary *)params 
               success:(SuccessBlock)success 
               failure:(FailuireBlock)failure{ }
复制代码

不应用换行:

- (void)requestWithUrl:(NSString*)url method:(NSString*)method params:(NSDictionary *)params success:(SuccessBlock)success failure:(FailuireBlock)failure{ }
复制代码

通过比拟能够看出,如果不应用换行,就很难一眼看清楚都是用了什么参数,而且代码整体看上去整洁洁净了很多。

列对齐

在申明一组变量的时候,因为每个变量名的长度不同,导致了在变量名左侧对齐的状况下,等号以及右侧的内容没有对齐:

NSString *name = userInfo[@"name"];
NSString *sex = userInfo[@"sex"];
NSString *address = userInfo[@"address"];
复制代码

而如果应用了列对齐的办法,让等号以及右侧的局部对齐的形式会使代码看上去更加整洁:

NSString *name    = userInfo[@"name"];
NSString *sex     = userInfo[@"sex"];
NSString *address = userInfo[@"address"];
复制代码

这二者的区别在条目数比拟多以及变量名称长度相差较大的时候会更加显著。

抉择一个有意义的程序

当波及到雷同变量(属性)组合的存取都存在的时候,最好以一个有意义的程序来排列它们:

  • 让变量的程序与对应的 HTML 表单中字段的程序相匹配
  • 从最重要到最不重要排序
  • 依照字母排序

举个例子:雷同汇合里的元素同时呈现的时候最好保障每个元素呈现程序是统一的。除了便于浏览这个益处以外,也有助于能发现漏掉的局部,尤其当元素很多的时候:

// 给 model 赋值
model.name      = dict["name"];model.sex       = dict["sex"];model.address = dict["address"];...
  
// 拿到 model 来绘制 UI
nameLabel.text    = model.name;
sexLabel.text     = model.sex;
addressLabel.text = model.address;
复制代码

把代码分成 ” 段落 ”

在写文章的时候,为了能让整个文章看起来构造清晰,咱们通常会把大段文字分成一个个小的段落,让表白雷同宗旨的语言凑到一起,与其余宗旨的内容分隔开来。

而且除了让读者明确哪些内容是表白同一宗旨之外,把文章分为一个个段落的益处还有便于找到你的浏览“足迹”,便于段落之间的导航;也能够让你的浏览具备肯定的节奏感。

其实这些情理同样实用于写代码:如果你能够把一个领有好几个步骤的大段函数,以空行 + 正文的办法将每一个步骤辨别开来,那么则会对读者了解该函数的性能有极大的帮忙。这样一来,代码既能有肯定的美感,也具备了可读性。其实可读性又何尝不是来自于规定,富裕美感的代码呢?

BigFunction{
  
     //step1:*****
     ....
       
     //step2:*****
     ...
        
     //step3:*****
     ....
  
}
复制代码

放弃格调一致性

有些时候,你的某些代码格调可能与公众比拟容易接受的格调不太一样。然而如果你在你本人所写的代码各处可能放弃你这种独有的格调,也是能够对代码的可读性有踊跃的帮忙的。

比方一个比拟经典的代码格调问题:

if(condition){

}
复制代码

or:

if(condition)
{

}
复制代码

对于下面的两种写法,每个人对条件判断右侧的大括号的地位会有不同的认识。然而无论你保持的是哪一个,请在你的代码里做到始终如一。因为如果有某几个特例的话,是十分影响代码的浏览体验的。

咱们要晓得,一个逻辑清晰的代码也能够因为留白的不规则,格局不对齐,程序凌乱而让人很难读懂,这是非常让人痛心的事件。所以既然你的代码在命名上,逻辑上曾经很优良了,就无妨再费一点功夫把她装扮的漂漂亮亮的吧!

如何写正文

首先援用书中的一句话:

正文的目标是尽量帮忙读者理解得和作者一样多。

在你写代码的时候,在脑海中可能会留下一些代码外面很难体现进去的局部:这些局部在他人读你的代码的时候可能很难领会到。而这些“不对称”的信息就是须要通过以正文的形式来通知浏览代码的人。

想要写出好的正文,就须要首先晓得:

  • 什么不能作为正文
  • 什么应该作为正文

什么不能作为正文

咱们都晓得正文占用了代码的空间,而且实际上对程序自身的运行毫无帮忙,所以最好保障它是 物有所值的

可怜的是,有一些正文是毫无价值的,它有情的占用了代码间的空间,影响了浏览代码的人的浏览效率,也节约了写正文的人的工夫。这样的正文有以下两种:

  • 形容能立即从代码本身就能立即了解的代码用意的正文
  • 给不好的命名增加的正文

形容能立即从代码本身就能立即了解的代码用意的正文

//add params1 and params2 and return sum of them
- (int)addParam1:(int)param1 param2:(int)param2
复制代码

下面这个例子举的比较简单,但反映的问题很显著:这外面的正文是齐全不须要的,它的存在反而减少了浏览代码的人的工作量。因为他从办法名就能够马上意会到这个函数的作用了。

给不好的命名增加的正文

//get information from internet
- (NSString *)getInformation
复制代码

该函数返回的是从网络获取的信息。但这里应用了 get 前缀,无奈看出信息的起源。为了补充信息,应用正文来补救。但其实这齐全不必要。只有取一个适当的名字就好了:

- (NSString *)fetchInformation
复制代码

讲完了正文不应该是什么内容,当初讲一下正文应该是什么样的内容:

什么应该作为正文

本书中介绍的正文大略有以下几种:

  • 写代码时的思考
  • 对代码的评估
  • 常量
  • 全局观的概述

写代码时的思考

你的代码可能不是欲速不达的,它的产生可能会须要一些思考的过程。然而很多时候代码自身却无奈将这些思考表达出来,所以你就可能有必要通过正文的形式来出现你的思考,让浏览代码的人晓得这段代码是哪些思考的结晶,从而也让读者了解了 这段代码为什么这么写。如果遇到了比你高超的高手,在他看到你的正文之后兴许会马上设计出一套更加适合的计划。

对代码的评估

有些时候你晓得你当初写的代码是个 长期的计划:它可能的确是解决以后问题的一个办法,然而:

  • 你晓得同时它也存在着某些缺点,甚至是陷阱
  • 你不晓得有其余的计划能够代替了
  • 你晓得有哪个计划能够代替然而因为工夫的关系或者本身的能力无奈实现

也可能你晓得你当初实现的这个计划简直就是“完满的”,因为如果应用了其余的计划,可能会耗费更多的资源等等。

对于下面这些状况,你都有必要写上几个字作为正文来诚恳的通知浏览你的这段代码的人这段代码的状况,比方:

// 该计划有一个很容易疏忽的陷阱:****
// 该计划是存在性能瓶颈,性能瓶颈在其中的 ** 函数中
// 该计划的性能可能并不是最好的,因为如果应用某某算法的话可能会好很多
复制代码

常量

在定义常量的时候,在其前面最好增加一个对于它是什么或者为什么它是这个值的起因。因为常量通常是不应该被批改的,所以最好把这个常量为什么是这个值阐明一下:

例如:

image_quality = 0.72 // 最佳的 size/quanlity 比率
retry_limit   = 4    // 服务器性能所容许的申请失败的重试下限
复制代码

全局观的概述

对于一个刚退出团队的新人来说,除了团队文化,代码标准以外,可能最须要理解的是以后被调配到的我的项目的一些“全局观”的意识:比方组织架构,类与类之间如何交互,数据如何保留,如何流动,以及模块的入口点等等。

有时仅仅增加了几句话,可能就会让新人迅速地理解以后零碎或者以后类的构造以及作用,而且这些也同样对开发过以后零碎的人员迅速回顾出之前开发的细节有很大帮忙。

这些正文能够在一个类的结尾(介绍这个类的职责,以及在整个零碎中的角色)也能够在一个模块入口处。书中举了一个对于这种正文的例子:

// 这个文件蕴含了一些辅助函数,尾门的文件系统提供了更便当的接口
复制代码

再举一个 iOS 开发里家喻户晓的网络框架 AFNetworking 的例子。在 AFHTTPSessionManager 的头文件里阐明了这个类的职责:

//AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths
复制代码

在晓得了什么不应该是正文以及什么应该是正文当前,咱们来看一下一个真正合格的正文应该是什么样子的:

正文该当有很高的信息 / 空间率

也就是说,正文应该用最简短的话来最明确地表白用意。要做到这一点须要做的致力是:

  • 让正文放弃紧凑:尽量用最简洁的话来表白,不应该有反复的内容
  • 精确地形容函数的行为:要把函数的具体行为精确表达出来,不能停留在表明
  • 用输出 / 输入的例子来阐明特地的状况:有时绝对于文字,可能用一个理论的参数和返回值就能立即体现出函数的作用。而且有些非凡状况也能够通过这个形式来揭示浏览代码的人
  • 申明代码的用意:也就是说明这段代码存在的意义,你为什么过后是这么写的起因

其实好的代码是自解释的,因为其命名的正当以及架构的清晰,简直不须要正文来向浏览代码的人增加额定的信息,书中有一个公式能够很形象地表明一个好的代码自身的重要性:

好代码 > (坏代码 + 正文)

三. 控制流和逻辑的改良

控制流在编码中占据着很重要的地位,它往往代表着一些外围逻辑和算法。因而,如果咱们能够让控制流变得看上去更加“天然”,那么就会对浏览代码的人了解这些逻辑甚至是整个零碎提供很大的帮忙。

那么都有哪相干实际呢?

  • 应用合乎人类自然语言的表白习惯
  • if/else 语句块的程序
  • 应用 return 提前返回

应用合乎人类自然语言的表白习惯

写代码也是一个表白的过程,尽管表现形式不同,然而如果咱们可能采纳合乎人类自然语言习惯的表白习惯来写代码,对浏览代码的人了解咱们的代码是很有帮忙的。

这里有两个比拟典型的情景:

  1. 条件语句中参数的程序
  2. 条件语句中的正负逻辑

条件语句中参数的程序:

首先比拟一下上面两段代码,哪一个更容易读懂?

//code 1
if(length > 10)

//code 2
if(10 < length)
复制代码

大家习惯上应该会感觉 code1 容易读懂。

再来看上面一个例子:

//code 3
if(received_number < standard_number) 

//code 4
if(standard_number< received_number)
复制代码

认真看会发现,和下面那一组状况相似,大多数人还是会感觉 code3 更容易读懂。

那么 code1 和 code3 有什么共性呢?

它们的共性就是:左侧都是被询问的内容(通常是一个变量);右侧都是用来做比拟的内容(通常是一个常量)

这应该是合乎自然语言的一个程序。比方咱们个别会说“明天的气温大于 20 摄氏度”,而不习惯说“20 摄氏度小于明天的气温”。

条件语句中的正负逻辑:

在判断一些正负逻辑的时候,倡议应用 if(result) 而不是if(!result)

因为大脑比拟容易解决正逻辑,比方咱们可能比拟习惯说“某某某是个男人”,而不习惯说“某某某不是个女人”。如果咱们应用了负逻辑,大脑还要对它进行取反,相当于多做了一次解决。

if/else 语句块的程序

在写 if/else 语句的时候,可能会有很多不同的互斥状况(好多个elseif)。那么这些互斥的状况能够遵循哪些程序呢?

  • 先解决掉简略的状况,后处理简单的状况:这样有助于浏览代码的人循序渐进地地了解你的逻辑,而不是一开始就吃掉一个瘦子,消耗不少精力。
  • 先解决非凡或者可疑的状况,后处理失常的状况:这样有助于浏览代码的人会马上看到以后逻辑的边界条件以及须要留神的中央。

应用 return 提前返回

在一个函数或是办法里,可能有一些状况是比拟非凡或者极其的,对后果的产生影响很大(甚至是终止持续进行)。如果存在这些状况,咱们应该把他们写在后面,用 return 来提前返回(或者返回须要返回的返回值)。

这样做的益处是能够缩小 if/else 语句的嵌套,也能够明确体现出:“哪些状况是引起异样的”。

再举一个 JSONModel 里的例子,在 initWithDictionary:error 办法外面就有很多 return 操作,它们都体现出了“在什么状况下是不能胜利将字典转化为 model 对象”的;而且在办法的最初返回了对象,阐明如果到了这一步,则在转化的过程中通过了层层考验:

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
    //check for nil input
    if (!dict) {if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }

    //invalid input, just create empty instance
    if (![dict isKindOfClass:[NSDictionary class]]) {if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an'NSDictionary'."];
        return nil;
    }

    //create a class instance
    self = [self init];
    if (!self) {

        //super init didn't succeed
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }

    //check incoming data structure
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}

    //import the data from a dictionary
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}

    //run any custom model validation
    if (![self validate:err]) {return nil;}

    //model is valid! yay!
    return self;
}
复制代码

四. 代码组织的改良

对于代码组织的改良,作者介绍了以下三种办法:

  • 抽取出与程序次要目标“不相干的子逻辑”
  • 从新组织代码使它一次只做一件事件
  • 借助自然语言形容来将想法变成代码

抽取出与程序次要目标“不相干的子逻辑”

一个函数外面往往蕴含了其主逻辑与子逻辑,咱们应该踊跃地发现并抽取出与主逻辑不相干的子逻辑。具体思考的步骤是:

  1. 首先确认这段代码的高层次指标是什么(次要指标)?
  2. 对于每一行代码,都要反思一下:“它是间接为了指标而工作么?”
  3. 如果答案是必定的并且这些代码占据着肯定数量的行数,咱们就应该将他们抽取到独立的函数中。

比方某个函数的指标是 为了寻找间隔某个商家最近的地铁口,那么这其中肯定会反复呈现一些计算两组经纬度之间间隔的子逻辑。然而这些子逻辑的具体实现是不应该呈现在这个主函数外面的,因为这些细节与这个主函数的指标来讲应该是无关的。

即是说,像这种相似于工具办法的函数其实是脱离于某个具体的需要的:它能够用在其余的主函数中,也能够放在其余的我的项目外面。比方 找到离运动场场最近的几个公交站 这个需要等等。

而像这种“抽取子逻辑或工具办法”的做法有什么益处呢?

  • 进步了代码的可读性:将函数的调用与原来简单的实现进行替换,让浏览代码的人很快能理解到该子逻辑的目标,让他们把注意力放在更高层的主逻辑上,而不会被子逻辑的实现(往往是简单无味的)所影响。
  • 便于批改和调试:因为一个我的项目中可能会屡次调用该子逻辑(计算间隔,计算汇率,保留小数点),当业务需要产生扭转的时候只须要扭转这一处就能够了,而且调试起来也非常容易。
  • 便于测试:同理,也是因为能够被屡次调用,在进行测试的时候就比拟有针对性。

从函数扩充到我的项目,其实在一个我的项目外面,有很多货色不是以后这个我的项目所专有的,它们是能够用在其余我的项目中的一些“通用代码”。这些通用代码能够对以后的我的项目无所不知,能够被用在其余任何我的项目中去。

咱们能够养成这个习惯,“把个别代码与我的项目专有代码离开”,并不断扩大咱们的通用代码库来解决更多的一般性问题。

从新组织代码使它一次只做一件事件

一个比拟大的函数或者性能可能由很多工作代码组合而来,在这个时候咱们有必要将他们分为更小的函数来调用它们。

这样做的益处是:咱们能够清晰地看到这个性能是如何一步一步实现的,而且拆分进去的小的函数或者也能够用在其余的中央。

所以如果你遇到了比拟难读懂的代码,能够尝试将它所做的所有工作列出来。可能马上你就会发现这其中有些工作能够转化成独自的函数或者类。而其余的局部能够简略的成为函数中的一个逻辑段落。

借助自然语言形容来将想法变成代码

在设计一个解决方案之前,如果你可能用自然语言把问题说分明会对整个设计十分有帮忙。因为如果间接从大脑中的想法转化为代码,可能会露掉一些货色。

然而如果你能够将整个问题和想法滴水不漏地说进去,就可能会发现一些之前没有想到的问题。这样能够不断完善你的思路和设计。

五. 最初想说的

这本书从变量的命名到代码的组织来解说了一些让代码的可读性进步的一些实际办法。

其实笔者认为代码的可读性也能够算作是一种沟通能力的一种体现。因为写代码的过程也能够被看做是写代码的人与浏览代码的人的一种沟通,只不过这个沟通是单向的:代码的可读性高,能够阐明写代码的人思路清晰,而且 TA 能够明确,高效地把本人的思考和工作内容以代码的模式表述进去。所以笔者置信能写出可读性很高的代码的人,TA 对于本人的思考和想法的形容能力肯定不会很差。

如果你真的打算好好做编程这件事件,倡议你从最小的事件上做起:好好为你的变量起个名字。不要再以“我英语不好”或者“没工夫想名字”作为遁辞;把态度端正起来,平时多动脑,多查字典,多看源码,天然就会了。

如果你连起个好的变量名都懒得查个字典,那你怎么证实你在遇到更难的问题的时候可能以迷信的态度解决它?如果你连编程里这种最小的事件都不好好做,那你又怎么证实你对编程是有谋求的呢?

正文完
 0