IT追梦者

Programming
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

The Art of Readable Code(summary)

Posted on 2012-07-09 09:05  星龙冰  阅读(328)  评论(0编辑  收藏  举报

今天读了第五章,看到目前为止感觉要写出易读代码,其中的精髓是:Put Yourself in the Reader’s Shoes。

写代码不仅仅为机器、为产品服务,还要站在代码阅读者的角度思考。而通常这三者是相辅相成,相互促进的。

 

 

PART I.表面改进

第一章:把信息尽量包含到命名之中

 1.使用具体含义的单词;fetch、download等比get要更加具体;

 2.避免使用一般性的名字;谨慎使用tmp、retral等通用性的命名;

 3.使用反应具体信息的命名;canListenOnPort要比canServerStart好;

 4.附加重要细节的前缀或后缀;elapse_ms,size_mb等有意义的前后缀;

 5.作用域较大的变量使用更长的能反应具体信息的名字;全局等较大作用域的变量尽量不使用缩写,而是具体完整的名字信息; 

 6.通过大小写、下划线等有意义的方式给名字赋予更多的信息;如大写变量表示构造器命名、下划线后面表示成员变量等;

 

第二章:避免令人误解的命名

 1.一些常见容易误解的单词:filter、length、limit;

 2.上下限(off_by_one),使用max_,mini_避免;

 3.开区间:(begin, end)和闭区间[first, last] ;

 4.命名boolean型名字:is、has;

 5.get、size等词表示轻量级的获取,给人的直觉是O(1)。如果包含复杂的计算工作(大于O(n))使用:calculate、count等词较好;

 

第三章:美学

 1.如果多个代码块完成类似的功能,使用整体一致的轮廓;

 2.保持代码格式缩进有助于浏览代码;

 3.保持代码呈现有意义的顺序,并在其他地方也严格遵守这种一致顺序的风格;

 4.使用空行按照逻辑把一大块代码打散分块(声明和实现);如下所示:

  1 class FrontendServer {

 2   public:
 3     FrontendServer();
 4     ~FrontendServer();
 5 
 6     // Handlers
 7     void ViewProfile(HttpRequest* request);
 8     void SaveProfile(HttpRequest* request);
 9     void FindFriends(HttpRequest* request);
10 
11     // Request/Reply Utilities
12     string ExtractQueryParam(HttpRequest* request, string param);
13     void ReplyOK(HttpRequest* request, string html);
14     void ReplyNotFound(HttpRequest* request, string error);
15 
16     // Database Helpers
17     void OpenDatabase(string location, string user);
18     void CloseDatabase(string location);
19 };

 

 def suggest_new_friends(user, email_password):

    # Get the user's friends' email addresses.
    friends = user.friends()
    friend_emails = set(f.email for f in friends)

    # Import all email addresses from this user's email account.
    contacts = import_contacts(user.email, email_password)
    contact_emails = set(c.email for c in contacts)

    # Find matching users that they aren't already friends with.
    non_friend_emails = contact_emails - friend_emails
    suggested_friends = User.objects.select(email__in=non_friend_emails) 

 

 第五章:

a.知道什么情况不需要注释;

b.像代码一样能够反应想法;

c.站在读者的角度,想象一下看他们需要知道哪些信息 ;

不要注释的情况: 

 1.阅读注释需要占用时间,注释本身需要占用屏幕空间,因此很容易从代码推断出的信息不要写注释;

 2.不要为了注释而注释;

 3.不要对不好的名字或者逻辑进行注释,而是改掉名字和优化逻辑;

注释什么:记录你的想法

 1.记录“导演评论”:记录你对代码有价值的观点;

 2.记录代码中存在的缺陷:如FIX、TODO、HACK。。。

 3.对常量进行注释;

站在读者的角度思考:

 1.预计读者可能存在的疑惑,针对进行注释 

 2.注释代码中可能存在的缺陷以及被误用的清醒

 3.蓝图总结性注释如下所示。如果一个新人需要了解代码可以从中整体把握(高度概括和精选的注释)

     • “This is the glue code between our business logic and the database. None of the application
     code should use this directly.”
     • “This class looks complicated, but it’s really just a smart cache. It doesn’t know anything
     about the rest of the system.”
 4.概括性注释:不必看每一行代码就能快速了解代码的意图;

SHOULD YOU COMMENT THE WHAT, THE WHY, OR THE HOW?

You may have heard advice like, “Comment the why, not the what (or the how).” Although catchy,
we feel these statements are too simplistic and mean different things to different people.
Our advice is to do whatever helps the reader understand the code more easily. This may involve
commenting the what, the how, or the why (or all three).

 5.写注释的基本步骤:写下脑海中的注释;阅读注释针对某些地方进行改进;总体改进;

 What not to comment:

• Facts that can be quickly derived from the code itself.
• “Crutch comments” that make up for bad code (such as a bad function name)—fix the
code instead.
Thoughts you should be recording include:
• Insights about why code is one way and not another (“director commentary”).
• Flaws in your code, by using markers like TODO: or XXX:.
• The “story” for how a constant got its value.
Put yourself in the reader’s shoes:
• Anticipate which parts of your code will make readers say “Huh?” and comment those.
• Document any surprising behavior an average reader wouldn’t expect.
• Use “big picture” comments at the file/class level to explain how all the pieces fit together.
• Summarize blocks of code with comments so that the reader doesn’t get lost in the details.

 

 第六章:Making Comments Precise and Compact(精简,精确简洁)

Comments should have a high information-to-space ratio

 1.注释应保持简短;(准确短小实例比文字描述可能效果更好)

 2.避免使用容易引起歧义的代词(it's....等)

 3.精确富有含义的注释

 4.精确的描述函数/方法的行为

 5.使用输入/输出的实例来说明一些极端情况

 6.说明你的代码的意图

 7.使用命名函数参数(Named Function Parameter)如:

 python:

    def Connect(timeout, use_encryption):

    ...
    # Call the function using named parameters
    Connect(timeout = 10, use_encryption = False)

 C++: 

    void Connect(int timeout, bool use_encryption) { ... }
    // Call the function with commented parameters
    Connect(/* timeout_ms = */ 10, /* use_encryption = */ false);
 注释应放在值的前面而不是后面。

 

 8.使用信息密集型词汇: 

 9.总结:

• Avoid pronouns like “it” and “this” when they can refer to multiple things.
• Describe a function’s behavior with as much precision as is practical.
• Illustrate your comments with carefully chosen input/output examples.
• State the high-level intent of your code, rather than the obvious details.
• Use inline comments (e.g., Function(/* arg = */ ... ) ) to explain mysterious function
arguments.
• Keep your comments brief by using words that pack a lot of meaning.

 

第二部份:简化循环以及代码逻辑 

 第七章:让控制流简单易读

 1.条件语句中参数的顺序问题;

  原则:左边一般放变化的即变量,右边放常量;如if(leftSize>10),if(receivedSize<expectedSize)

  有一个特例是:相等性比较有时采用if(null == obj) c、c++等语言为了防止bug故意采用这种写法。

 2.if/else语句块的组织顺序,遵循三个原则:

   a.优先处理正向条件,else负向条件;if(debug) > if(!debug)

   b.先处理简单的情况,else复杂情况;这样可以一次性把所有情况都在一个显示屏下看到;if(simple) else (complex)

   c.先处理感兴趣的或者显著的情况,else其他情况;if(interesing or conspicuous) else ....

   d.最经常执行的放前面(一般能够满足a b c就可以满足d)

 3.?:三元条件表达式使用原则:不是以代码行数最少,而是要能在最短时间看明白为标准。所以?:一般用于语句条件简单的情形

 4.避免使用do while语句

 5.从函数中尽快的返回值:

  public boolean Contains(String str, String substr) {

   if (str == null || substr == nullreturn false;
   if (substr.equals("")) return true;

 6.大部分时候应该避免使用goto语句;

 7.最小化嵌套语句的深度:

  a.通过尽快返回减少嵌套:

    if (user_result != SUCCESS) {

   reply.WriteErrors(user_result);
   reply.Done();
   return;
}
if (permission_result != SUCCESS) {
   reply.WriteErrors(permission_result);
   reply.Done();
   return;

 b.消除循环中的嵌套:

  for (int i = 0; i < results.size(); i++) {

   if (results[i] != NULL) {
      non_null_count++;
      if (results[i]->name != "") {
         cout << "Considering candidate..." << endl;
      }
   }
}



for (int i = 0; i < results.size(); i++) {
   if (results[i] == NULL) continue;
      non_null_count++;
   if (results[i]->name == "") continue;
      cout << "Considering candidate..." << endl;
   ... 

 8.不易理清的执行流:线程、信号/中断处理器、异常、函数指针和匿名函数、虚函数。

 

第八章 分解大型表达式

 1.将大型表达式分解成易于理解的片段;具体有如下方法:

    a.使用解释性变量:if line.split(':')[0].strip() == "root"  ----->>> username = line.split(':')[0].strip() if(username == "root")

    b.使用概括性变量:if (request.user.id != document.owner_id)  ----->>> final boolean user_owns_document = (request.user.id == document.owner_id);
    c.根据的摩根定律分解布尔表达式: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)

    d.小心短路逻辑表达式的使用:assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

    e.分解大型语句:

 

    f.其他创造性简化语句的方法:add_to->set_XXX(add_from.XXX() + add_to->XXX()); --

      在C++中可以通过宏来简化表达式:#define ADD_FIELD(field) add_to->set_##field(add_from.field() + add_to->field())

 

第九章 变量及可读性问题:变量越多越难以跟踪他们的使用;变量作用域越大跟踪变量时间空间就越长;变量变化越频繁越难以跟踪当前值是多少;(FP中貌似可以很大程度消除这些问题)

 1. 消除变量:

   a.少用临时变量它无法分解复杂的表达式、没有增加表达的清晰性、只使用一次因此也不能压缩代码;

   b.消除控制逻辑变量;

   c.尽可能的减小变量的作用域; 

   d.尽量使用静态方法;

   e.分解类,把大类分解成小的类;

   f.C++中使用if语句作用域:if (PaymentInfo* info = database.ReadPaymentInfo()) {

     Java中while((String readLine = reader.readLine())!=null)

   g.JavaScript中的私有变量;

   h.尽量将变量定义靠近变量使用的地方;

   i.尽量使用一次性写入的变量;

  Summary:

   • Eliminate variables that just get in the way. In particular, we showed a few examples

of how to eliminate “intermediate result” variables by handling the result immediately.
   • Reduce the scope of each variable to be as small as possible. Move each variable to a
place where the fewest lines of code can see it. Out of sight is out of mind.
   • Prefer write-once variables. Variables that are set only once (or const, final, or
otherwise immutable) make code easier to understand.

 

第三部份 重新组织你的代码 

a.抽取出跟你程序主要目标不相关的问题独立出来;

b.重新组织你的代码使它一次只做一件事; 

c.首先使用文字描述你的代码,通过描述获取更加简洁的解决方案。

工程是关于把大问题分解成足够小的多个子问题,然后分别解决子问题。 

第十章 抽取不相关的子问题:积极识别并提取出无关的子问题。

 1.就一个方法或者代码块的弄清楚其最高目标是什么?

 2.针对每一行代码都要问:它是否直接服务于最高目标,或者是否在解决无关的子问题?

 3.如果多行代码都在解决一个无关的子问题,将其抽取成独立的方法;

 ------------------------------------------------------------

 a.构建自己的Utility代码库、创建大量通用功能的代码库、简化已有接口、按需重塑接口

总之将特定工程的代码和通用代码进行分离。 

关于自底向上和自顶向下: 

Top-down programming is a style where the highest-level modules and functions are designed first
and the lower-level functions are implemented as needed to support them.
Bottom-up programming tries to anticipate and solve all the subproblems first and then build the
higher-level components using these pieces.

第十一章 一次只做一件事

代码应该一次只做一件事;

1.任务足够的小;

2.从对象中抽取出值;

 

第十二章 把思维变成代码

You do not really understand something unless you can explain it to your grandmother.
                                                                          —Albert Einstein
技巧就是向你的读者讲述你的代码:

1.用平实的语言向同事描述代码需要做的事情;

2.注意描述中采用的关键词和短语;

3.写下跟你描述相符的代码;

 清晰的描述你逻辑、熟悉使用库的帮助文档、即使对于大的问题也可以采用描述的方式帮助你分解成小问题、递归的使用此方法、

 

第十三章 写更少的代码:The most readable code is no code at all.

 1.你是否真正需要此功能;

 2.质疑和分解你的需求;

 3.保持足够小的代码基; 

   as any coordinated system grows,the complexity needed to keep it glued together grows even faster。(就像任何协调系统增长一样,保持把系统凝聚在一起的复杂度增长的速度往往要更快),因此要尽量保持代码基精简轻量。具体方式有:

   a.创建尽可能多的通用工具代码,减少重复。

   b.删除无用的代码或者很少 用到的特性。

   c.将你的项目划分成无关联的子项目。

   d.有代码重量保持轻量的意识。 

   e.熟悉你所用到的库。every once in a while, spend 15 minutes reading the names of all the functions/modules/types in your standard library

   f.重用代码是一种双赢。 

删除无用代码:就像园丁裁剪老的树枝,才能长出新树枝。要删除无用的代码,需求阶段就裁剪掉代价最小,后面裁剪也可以保持代码足够小易于阅读以及维护。

总结:

 • Eliminating nonessential features from your product and not overengineering

 • Rethinking requirements to solve the easiest version of the problem that still gets the job
done
 • Staying familiar with standard libraries by periodically reading through their entire APIs
 

 

第三部分 挑选的主题

第十七章 可测性和可读性:Test code should be readable so that other coders are comfortable changing or adding tests

1.Make Tests Easy to Read and Maintain:hide less important details from the user, so that more important details are most prominent.
2.Making Error Messages Readable

3.Choosing Good Test Inputs(In general, you should pick the simplest set of inputs that completely exercise the code.)

好的Test:
1. The test is very long and full of unimportant details. You can describe what this test is
doing in one sentence, so the test statement shouldn’t be much longer.
2. Adding another test isn’t easy. You’d be tempted to copy/paste/modify, which would make
the code even longer and full of duplication.
3. The test failure messages aren’t very useful. If this test fails, it will just say Assertion failed:
docs.size() == 3, which doesn’t give you enough information to debug it further.
4. The test tries to test everything at once. It’s trying to test both the negative filtering and
the sorting functionality. It would be more readable to break this into multiple tests.
5. The test inputs aren’t simple. In particular, the example score -99998.7 is “loud” and gets
your attention even though there isn’t any significance to that specific value. A simpler
negative value would suffice.
6. The test inputs don’t thoroughly exercise the code. For example, it doesn’t test when the
score is 0. (Would that document be filtered or not?)
7. It doesn’t test other extreme inputs, such as an empty input vector, a very large vector, or
one with duplicate scores.
8. The name Test1() is meaningless—the name should describe the function or situation
being tested.
测试驱动开发:TEST-DRIVEN DEVELOPMENT

过于专注的测试:

• Sacrificing the readability of your real code, for the sake of enabling tests.
Designing your real code to be testable should be a win-win situation: your real code
becomes simpler and more decoupled, and your tests are easy to write. But if you have to
insert lots of ugly plumbing into your real code just so you can test it, something’s wrong.
• Being obsessive about 100% test coverage. Testing the first 90% of your code is often
less work than testing that last 10%. That last 10% might involve user interface, or dumb
error cases, where the cost of the bug isn’t really that high and the effort to test it just isn’t
worth it.
The truth is that you’ll never get 100% coverage anyhow. If it’s not a missed bug, it might
be a missed feature or you might not realize that the spec should be changed.
Depending on how costly your bugs are, there’s a sweet spot of how much development
time it’s worth spending on test code. If you’re building a website prototype, it might not
be worth writing any test code at all. On the other hand, if you’re writing a controller for
a spaceship or medical device, testing is probably your main focus.
• Letting testing get in the way of product development. We’ve seen situations where
testing, which should be just one aspect of a project, dominates the whole project. Testing
becomes some sort of god to be appeased, and coders just go through the rituals and
motions without realizing that their precious engineering time might be better spent
elsewhere.

改善你的测试:

• The top level of each test should be as concise as possible; ideally, each test input/output can be described in one line of code.
• If your test fails, it should emit an error message that makes the bug easy to track down and fix.
• Use the simplest test inputs that completely exercise your code.
• Give your test functions a fully descriptive name so it’s clear what each is testing. Instead of Test1(), use a name like Test_<FunctionName>_<Situation>.

 

 

 

 

     

 


 


 

 总结与整理:

一.书本中出现的黑体字语句:

 

 

二.书本中出现的斜体字语句: