## 第一部分 表面层次的改进 从“表面层次”改进代码,这一部分不涉及 代码的重构。主要包括: 1. 选择好的 方法名、函数名; 2. 写好的注释 3. 代码的整洁性。 ### 1. 选择准确的变量名 #### 1.1 准确的动词 案例1: def getPage(url) 这个函数名 用get就不明显,是从 数据库中得到一个网页呢?还是从 互联网上得到一个网页?要是从互联网中得到一个页面,可以考虑用 fetchPage() 或者 downloadPage; 案例2: 一段操作数据库结果的代码 ``` results = Database.all_objects.filter("year <= 2011") ``` 这里的filter指代的是 “挑出”,还是减少呢,从代码中其实看不出来的。 一些常用词及其替代词汇 ``` send: deliver、dispatch、announce、distribute、route find: search、extract、locate、discover start: launch、create、begin、open make:create、set up、build、generate、compose、add、new ``` #### 1.2 准确的变量 案例1:类 BinaryTree 定义如下: ```JAVA class BinaryTree{ int size; } ``` 这里的 Size就很不明显,是期望返回的是 树的高度,节点数,还是树在内存中所占的空间? 对应应该用更专业的词,Height, NumNodes 或者 MemoryBytes。 几点变量命名建议: - 推荐用 min 和 max来表示(包含)极限 - 推荐用 first 和 last 来表示包含的范围 - 表示 布尔值的时候,加上 is、has、can、should ### 2. 避免使用 temp / retval / result 不要用 temp 或者 retval 这样的词。因为这种包括不了更多的信息。(temp这个名字只应用于 短期存在 且 临时性 为其存在因素的变量) ### 3. 避免在迭代器中使用 i/j/k 因为要是用了多重循环,就很难发现错误。 ``` if(clubs[i].members[k] == users[j])` -> `if(clubs[ci].members[mi] == users[ui]) ``` ### 4. 为度量变量加上单位 如果变量是一个度量的话(比如 时间长度、字节数),那么最好把名字带上它的单位 ``` var start = (new Date()).getTime()` -> `var start_ms = (new Date()).getTime(); ``` | before | after | | ----------------------------- | ---------------------- | | satrt(int delay) | delay -> delay_desc | | createCache(int six) | size -> size_mb | | throttleDownload(float limit) | limit -> max_kbps | | truncate(text, max_length) | maxLength -> max_chars | ### 5. 代码美学 选用列对齐,可以比较整洁,也可以更直接的发现拼写错误 ```java details = request.POST.get('detail') location = request.POST.get('location') phone = equest.POST.get('phone') email = request.POST.get('email') url = request.POST.get('url') ``` 把代码分成 "段落"。 ```CSHARP class FrontendServer { public: FrontServer(); ~FrontServer(); //Handlers void ViewProfile(HttpRequest* request); void SaveProfile(HttpRequest* request); void FindFriends(HttpRequest* request); //Request/Reply Utilities string ExtractQueryParam(HttpRequest* request, string param); void ReplyOK(HttpRequest* request, string html); void ReplyNotFound(HttpRequest* request, string error); // Database Helpers void OpenDatabase(string location, string user); void CloseDatabase(string location); } ``` ### 6. 注释 KEY IDEA > Comments should have a high information-to-space ratio - 不要添加不必要的注释(从代码就能读出来的含义) #### 6.1 不要为了注释而注释 ```CSHARP // Find the Node in the givin subtree, with the given name, using the given depth Node* FindNodeInSubtree(Node* subtree, string name, int depth) ``` 添加细节: ```csharp // Find a Node with given 'name' or return NULL. // If depth <=0, only 'subtree' is inspected. // If depth == N, only 'subtree' and N levels below are inspected. Node* FindNodeInSubtree(Node* subtree, string name, int depth) ``` #### 6.2 用注释记录你的想法 标注代码瑕疵 ``` TODO: Stuff I haven’t gotten around to yet FIXME: Known-broken code here HACK: Admittedly inelegant solution to a problem XXX: Danger! major problem here ``` 每个团队都有自己的风格,比如 TODO:只用于重要的问题,也可以用 todo: 或者 mayber-later: 来表示重要的缺陷 #### 6.3 给常量添加注释 ```JAVA // Impose a reasonable limit - no human can read that much anyway. const int MAX_RSS_SUBSCRIPTION = 1000; ``` #### 6.4 写出言简意赅的注释 让注释保持紧凑 ```C // The int is the CategoryType // The first float in the inner pari in the 'score', // the second is the 'weight' typedef hash_map> ScoreMap; ``` #### 6.5 不要用代词 会导致含义不明。 案例1; // Insert the data into the cache, but check if it's too big first. 那这里的it指的是数据呢?还是缓存呢? #### 6.6 举例来说明特别的情况 //... // Example: Stripe("abba/a/ba", "ab") returns "/a/" String Stripe(String src, String chars) { ... } ## 第二部分 简化逻辑 > 关键思想:把条件、循环以及其他对控制流的改变做得越“自然”越好。运用一种方式使读者不用停下来重读你的代码。 > > Key Idea: Make all your conditionals, loops, and other changes to control flow as "natural" as possible - written in a way that doesn't make the reader stop and reread your code. ### 7. 把控制流变得易读 #### 7.1 if 比如我们常用的习惯是 `if(length >= 10)`, `while(bytes_actual > bytes_expected)` 建议: 比较式的左侧:不断变化的 比较式的右侧:常量 #### 7.2 if else 原则 - 先处理 正逻辑,再处理负逻辑。比如 用 `if(debug)` 而不是 `if(!debug)` 用 三目运算符,替代简单的 if else ```java time_str += (hour >=12) ? "pm" :"am"; ``` #### 7.3 return ```java public boolean Contains(String str, String subStr){ if(str == null || substr == null) return false; if(substr.equals("")) return true; } ``` #### 7.4 减少嵌套 嵌套会 大大增加读者的思维负担。 ```java if(user_result == SUCCESS) { if(premission_result != SUCESS){ reply.WriteErrors("error reading permissions"); reply.Done(); return; } reply.WriteErrors(""); else{ reply.WriteErrors(user_result); } reply.Done(); } ``` 以上的代码很有可能是 以前的需求是只需要判断一下 "user_result",现在增加了新的需求,还要判断 "premission_result"。 可以改为 ```java if(user_result != SUCCESS){ reply.WriteError(user_result); reply.Done(); return; } if(permission_result != SUCCESS){ reply.WriteError(permission_result); reply.Done(); return; } reply.WriteErrors(""); reply.Done(); ``` ### 8. 拆分超长的表达式 #### 8.1 抽离变量 用变量代替 表达式 里面的东西 ```java final boolean users_owns_document = (request.user.id == document.owner.id); if(user_owns_document) { // user can edit this document... } if(!user_owns_document) { // document is read-only... } ``` #### 8.2 德摩根定律 !(a||b) 等于 !a&&!b,!(a&&b) 等于 !a||!b 后者比前者的可读性更好。 ### 9. 变量与可读性 #### 9.1 减少变量 可以考虑删除的变量: 1. 临时变量,也就是 tmp 变量; 2. 控制流变量,也就是 ```java boolean done = false; while(!done){ if(...){ done = true; continue; } } ``` 应该考虑改成 ```java while( /* condition*/){ ... if(...){ break; } } ``` #### 9.2 减少变量的作用域 要是一个变量,只有一个方法可以用到,就应该把这个变量变为局部变量。 ```JAVA class LargeClass{ string str_; void Method1(){ str_ = ...; Method2(); } void Method1(){ // Uses str_ } } ``` 就可以改成 ```JAVA class LargeClass{ void Method1(){ string str_ = ...; Method2(); } void Method1(){ // Uses str_ } } ``` 要是一个变量用在if里面,可以适当进行简练。 ```C++ if(Payment* info = database.ReadPaymentInfo()){ cout << "Use pard: " << info ->amount() <