编写优雅代码的最佳实践

Robert Martin曾说过"在代码阅读中说脏话的频率是衡量代码质量额唯一标准"。同时,代码的写法应当使别人理解它所需的时间最小化,也就是说我们写的代码是给人看的而不是给机器看的。那么,如何编写优雅代码呢?可以从思想层面和具体技巧层面来优化代码,思想层面指的是遵循面向对象设计原则,本期介绍的是具体技巧。

1. 代码总是越短越好吗?

assert((!(bucket = findBucket(key))) || !bucket.isOccupied());

上面这行代码虽然比较短,但是难以阅读。为了更好地阅读,我们做如下修改:

bucket = findBucket(key);
if(bucket != null){
  assert(!bucket.isOccupied());
}

减少代码行数是一个好目标,但是让阅读代码的事件最小化是个更好的目标

2. 给重要语句添加注释

// Fast version of "hash = (65599*hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c

上面这行代码如果没有添加注释,我们根本不知道是什么意思,但是有了这行注释,我们就知道通过移位操作来提升性能。

3. tmp的使用

tmp是我们经常用的,譬如说两个变量置换,都已变成约定俗成了。

tmp = right;
right = left;
left = tmp;
String tmp = user.getName();
tmp += " " + user.getPhoneNumber();
tmp += " " + user.getEmail();
template.set("user_info",tmp);

4.i,j,k,iter,it:只用做索引或者循环迭代

i,j,k,iter,it被用做索引或者循环迭代已成为业界规范了(i是index的缩写),例如:

for(int i=0;i<100;i++){
  for(int j=0;j<100;j++){
    ......
  }
}

Iterator<String> iter = list.iterator();
while(iter.hasNext()){
  ......
}

如果我们在其他地方使用i,j,k,那么就会增加阅读者的时间。

5. 附带重要属性

我们把命名当做一种注释的方式,让它承载更多的信息!


image.png

6. 名字需要多长?

  • 在小的作用域中使用简短的名字
  • 在作用域大的可以使用长名字
if(debug){
  Map<String,Integer> m = new HashMap<>();
  lookUpNamesNumbers(m);
  print(m);
}

7. 不要使用容易误解的名字

results = Database.all_objects.filter("year<=2011")

上面这行代码结果现在包含哪些信息?filter是把年份小于等于2011年的数据过滤掉?还是保留?

8. 推荐用min和max来表示极限

MAX_ITEMS_IN_CART = 10;

if (shoppingCart.numOfItems()> MAX_ITEMS_IN_CART){
    error("Too many items in cart");
}

9. 推荐用begin和end来表示包含/排除范围

image.png

begin表示包含,end表示排除,在Java中典型的例子就是String.substring()


String s = "Hello world";

s.substring(2,5);-> "llo"

10.与使用者的期望相匹配

一般来说,getter方法就是获取一个字段的值,用户期待的是轻量级的方法,如果你要是在其中做了太多的计算,就应该考虑改名。


public double getMeanPrice(){

//遍历所有条目计算总价,然后计算平均价格

}

public double computeMeanPrice(){

//遍历所有条目计算总价,然后计算平均价格

}

11.不要为那些从代码本身就能快速推断的事实写注释


public  class Account {  

    // Constructor

   public  Account(){

   }

   // Set the profit member to a new value    

   void setProfit(double profit){

         …….

   }   

    // Return the profit from this Account    

    double getProfit(){

        ….

    }

};

12. 不要给不好的名字加注释--应该把名字改好

// Releases the handle for this key.This doesn't modify the actual registry.
void deleteRegistry(RegistryKey key)

乍一看我们会误认为这是一个删除注册表的函数,可是注释里澄清它不就改动真正的注册表。因此,我们可以用一个更加自我说明的名字,例如:

void releaseRegistryHandle(registryKey key);

13.为代码中的瑕疵写注释

// TODO:采用更快算法或者当代码没有完成时
// TODO(dustin):处理除JPEG以外的图像格式


image.png

14.为常量写注释

// users thought 0.72 gave the best size/quality tradeoff
image_quality = 0.72;

// as long as it's >= 2*num_processors,that's good enough
NUM_THREADS = 8;

// impose a reasonable limit - no human can read that much anywhere
const int MAX_RSS_SUBSCRIPTIONS = 1000;

15. 站在读者的角度写注释

struct Recoder {
    vector<float> data;
    ......
    void clear(){
        // 每个人读到这里都会问,为啥不直接调用data.clear()
        vector<float>().swap(data);
    }
}

如果有一个好的注释可以解答读者的疑问,将上述进行如下修改:强制Vector真正地把内存归还给内存分配器,详情请查阅STL swap trick。

16. 公布可能的陷阱

void sendMail(String to,String subject,String body);

这个函数由于需要调用外部服务器发送邮件,可能会很耗时,有可能导致使用者的线程挂起。需要将这段描述放到注释中。

17. 条件语句中参数的顺序

image.png

一般原则:将变量放在左边,常量放在右边。更宽泛地说,将比较稳定的变量放在右边,变化较大的放在左边。如 if ( length >= 10) 而不是 if ( 10 <= length)。但是,在非“大小”比较的情况下,上面的原则似乎不起作用,例如验证一个请求参数是否为某个特定值:if ( request.getParameterValue("name")).equals("Brandon")),此时将常量"Brandon"可以避免出现空指针的情况(上行的参数没有name或者值为空)。

18. if/else语句块的顺序

if/else书写规范:首先处理正逻辑而不是负逻辑,例如 if(ok),而不是if(!ok);其次处理掉简单的情况,这有利于让if和else处理代码在同一个屏幕内可见。

19. 通过提早返回减少嵌套

使用提前返回的机制,可以把函数的嵌套层级变浅。举个栗子,没有使用提前返回的代码:

static bool checkUserAuthority()
        {
            bool a, b, c, d, e;

            if (a)
            {
                if (b)
                {
                    if (c)
                    {
                        if (d)
                        {
                            if (e)
                            {
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

使用了提前返回的代码:

static bool checkUserAuthority()
        {
            bool a, b, c, d, e;

            if (!a)
                return false;

            if (!b)
                return false;

            if (!c)
                return false;

            if (!d)
                return false;

            if (!e)
                return false;

            return true;
       
        }

20. 通过 "总结变量" 增加可读性

if(request.user.id == document.owner_id){
    // user can edit this document ...
}

if(request.user.id != document.owner_id){
    // document is read-only...
}

通过观察,我们提取一个变量final boolean user_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...
}

21. 减少控制流变量

在while、for等循环语句中,我们通常使用自定义的bool变量,来控制流转。

boolean done = false;
while(/* condition */ && !done){
    ...
    if(...){
        done = true;
        continue;
    }
}

以我们的经验,"控制流变量" 可以通过优化程序结构、逻辑来消除。

while(/* condition */){
    ...
    if(...){
        break;
    }
}

22. 缩小变量的作用域

void foo(){
    int i = 7;

    if(someCondition){
        // i is used only within this block
    }

}

void foo(){
    if(someCondition){
        int i = 7;
        // i is used only within this block
    }
}

23. 不要为了共享而把变量设置为类的字段

public class LargeClass{
    String s;
    void method1(){
        s = ...
        method2();
    }
    void method2(){
        //使用s
    }
}

通过参数传递来实现数据共享

public class LargeClass{
    void method1(){
        String s = ...
        method2(s);
    }
    void method2(String s){
        //使用s
    }
}

24. 不要把所有变量都定义在开头

把所有变量定义在开头是C语言的风格,面向对象语言习惯将变量定义在离它开始使用的地方。

public void foo(){
  boolean debug = false;
  String[] pvs;
  String pn;
  String pv;
  ...
}

除了上述建议之外,我们还可以参考阿里Java规范,关注微信号:"木可大大",发送"阿里Java规范"即可获得相关资料。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容