查看: 2959|回复: 7
收起左侧

[其他] 代码的味道 -- 编写可读代码的艺术 笔记

    |只看干货
本楼: 👍   100% (23)
 
 
0% (0)   👎
全局: 👍   89% (2318)
 
 
10% (283)    👎

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
强烈推荐此书《The Art of Readable Code》的读书笔记,花个把小时就可以显著提升你的代码质量
https://pegasuswang.readthedocs. ... 8%89%BA%E6%9C%AF/#4

最好就是写更少的代码,而最易懂的代码就是没有代码!下面摘抄简单入门的

"Code should be written to minimize the time it would take for someone else to understand it."

如何命名

使用含义明确的词,比如用`download`而不是`get`,参考以下替换方案:

     send -> deliver, dispatch, announce, distribute, route
     find -> search, extract, locate, recover
    start -> lanuch, create, begin, open
     make -> create,set up, build, generate, compose, add, new

使用具体的名字

`CanListenOnPort`就比`ServerCanStart`好,can start比较含糊,而listen on port确切的说明了这个方法将要做什么。

`--run_locally`就不如`--extra_logging`来的明确。

增加重要的细节,比如变量的单位`_ms`,对原始字符串加`_raw`

    password  ->  plaintext_password
     comment  ->  unescaped_comment
        html  ->  html_utf8
        data  ->  data_urlenc

使用`min`、`max`代替`limit`

    CART_TOO_BIG_LIMIT = 10
        if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT:
            Error("Too many items in cart.")

    MAX_ITEMS_IN_CART = 10
        if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
         Error("Too many items in cart.")

对比上例中`CART_TOO_BIG_LIMIT`和`MAX_ITEMS_IN_CART`,想想哪个更好呢?

Boolean型变量命名

    bool read_password = true;

这是一个很危险的命名,到底是需要读取密码呢,还是密码已经被读取呢,不知道,所以这个变量可以使用`user_is_authenticated`代替。通常,给Boolean型变量添加`is`、`has`、`can`、`should`可以让含义更清晰,比如:

                 SpaceLeft()  -->  hasSpaceLeft()
    bool disable_ssl = false  -->  bool use_ssl = true

      public double getMean() {
            // Iterate through all samples and return total / num_samples
        }


在这个例子中,`getMean`方法遍历了所有的样本,返回总额,所以并不是普通意义上轻量的`get`方法,所以应该取名`computeMean`比较合适。

怎么写注释

// Calls an external service to deliver email.  (Times out after 1 minute.)
    void SendEmail(string to, string subject, string body);

有时候为了更清楚说明,需要给整个文件加注释,让读者有个总体的概念:

    // This file contains helper functions that provide a more convenient interface to our
    // file system. It handles file permissions and other nitty-gritty details.

按照函数的节奏,写一些注释:

    def GenerateUserReport():
        # Acquire a lock for this user
        ...
        # Read user's info from the database
        ...
        # Write info to a file
        ...
        # Release the lock for this user

精简注释

    // The int is the CategoryType.
    // The first float in the inner pair is the 'score',
    // the second is the 'weight'.
    typedef hash_map<int, pair<float, float> > ScoreMap;

这样写太罗嗦了,尽量精简压缩成这样:

    // CategoryType -> (score, weight)
    typedef hash_map<int, pair<float, float> > ScoreMap;

条件语句中参数的位置
对比下面两种条件的写法:

    if (length >= 10)
    while (bytes_received < bytes_expected)

    if (10 <= length)
    while (bytes_expected > bytes_received)

到底是应该按照大于小于的顺序来呢,还是有其他的准则?是的,应该按照参数的意义来

<li>运算符左边:通常是需要被检查的变量,也就是会经常变化的</li>
<li>运算符右边:通常是被比对的样本,一定程度上的常量</li>

`bytes_received < bytes_expected`比反过来更好理解。

三目运算符(?:)

    time_str += (hour >= 12) ? "pm" : "am";

    Avoiding the ternary operator, you might write:
        if (hour >= 12) {
            time_str += "pm";
        } else {
            time_str += "am";
    }

尽早return

    public boolean Contains(String str, String substr) {
        if (str == null || substr == null) return false;
        if (substr.equals("")) return true;
        ...
    }

函数里面尽早的return,可以让逻辑更加清晰。

逻辑替换

- 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)) Error("Sorry, could not read file.");

    //替换
    if (!file_exists || is_protected) Error("Sorry, could not read file.");

####不要滥用逻辑表达式

    assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

这样的代码完全可以用下面这个替换,虽然有两行,但是更易懂:

    bucket = FindBucket(key);
    if (bucket != NULL) assert(!bucket->IsOccupied());

消除条件控制变量

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

这里的`done`可以用别的方式更好的完成:

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

不要使用嵌套的作用域

    # No use of example_value up to this point.
    if request:
        for value in request.values:
        if value > 0:
            example_value = value
            break

    for logger in debug.loggers:
        logger.log("Example:", example_value)

这个例子在运行时候会报`example_value is undefined`的错,修改起来不算难:

    example_value = None
    if request:
        for value in request.values:
            if value > 0: example_value = value
            break

    if example_value:
        for logger in debug.loggers:
        logger.log("Example:", example_value)

但是参考前面的**消除中间变量**准则,还有更好的办法:

    def LogExample(value):
        for logger in debug.loggers:
            logger.log("Example:", value)

        if request:
            for value in request.values:
                if value > 0:
                    LogExample(value)  # deal with 'value' immediately
                    break

用到了再声明
在C语言中,要求将所有的变量事先声明,这样当用到变量较多时候,读者处理这些信息就会有难度,所以一开始没用到的变量,就暂缓声明

业务相关的函数
那些与目标不相关函数,抽离出来可以复用,与业务相关的也可以抽出来,保持代码的易读性,例如:

    business = Business()
    business.name = request.POST["name"]

    url_path_name = business.name.lower()
    url_path_name = re.sub(r"['\.]", "", url_path_name)
    url_path_name = re.sub(r"[^a-z0-9]+", "-", url_path_name)
    url_path_name = url_path_name.strip("-")
    business.url = "/biz/" + url_path_name

    business.date_created = datetime.datetime.utcnow()
    business.save_to_database()

抽离出来,就好看很多:

    CHARS_TO_REMOVE = re.compile(r"['\.']+")
    CHARS_TO_DASH = re.compile(r"[^a-z0-9]+")

    def make_url_friendly(text):
        text = text.lower()
        text = CHARS_TO_REMOVE.sub('', text)
        text = CHARS_TO_DASH.sub('-', text)
        return text.strip("-")

    business = Business()
    business.name = request.POST["name"]
    business.url = "/biz/" + make_url_friendly(business.name)
    business.date_created = datetime.datetime.utcnow()
    business.save_to_database()

把想法转换成代码
要把一个复杂的东西解释给别人,一些细节很容易就让人产生迷惑,所以想象把你的代码用平实的语言解释给别人听,别人是否能懂,有一些准则可以帮助你让代码更清晰:


  • 用最平实的语言描述代码的目的,就像给读者讲述一样
  • 注意描述中关键的字词
  • 让你的代码符合你的描述

下面这段代码用来校验用户的权限:

    $is_admin = is_admin_request();
    if ($document) {
        if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
            return not_authorized();
        }
    } else {
        if (!$is_admin) {
            return not_authorized();
        }
    }
    // continue rendering the page ...

这一段代码不长,里面的逻辑嵌套倒是复杂,参考前面章节所述,嵌套太多非常影响阅读理解,将这个逻辑用语言描述就是:

    有两种情况有权限:
    1、你是管理员(admin)
    2、你拥有这个文档
    否则就没有权限

根据描述来写代码:

    if (is_admin_request()) {
        // authorized
    } elseif ($document && ($document['username'] == $_SESSION['username'])) {
        // authorized
    } else {
        return not_authorized();
    }
    // continue rendering the page ...




评分

参与人数 25大米 +31 收起 理由
aranne + 1 赞一个
微信用户_e573997 + 1 欢迎分享你知道的情况,会给更多积分奖励!
nitsc + 1 很有用的信息!
dnalwqer + 2 给你点个赞!
蘑菇331 + 1 给你点个赞!
Killua1222 + 1 赞一个
14417335 + 5 给你点个赞!
Falldawn + 1 给你点个赞!

查看全部评分


上一篇:算法面试必刷100题
下一篇:分享一个notion记录刷题的模板

本帖被以下淘专辑推荐:

toughcharlie 2022-5-2 23:30:45 | 显示全部楼层
本楼: 👍   100% (1)
 
 
0% (0)   👎
全局: 👍   93% (1075)
 
 
6% (69)    👎
说的很好了。

但是更高级别的可读性,可能还是要依赖于程序员的抽象的功力 – 如同 SICP 中贯穿的思想。

评分

参与人数 1大米 +1 收起 理由
Chasedream.df + 1 赞一个

查看全部评分

回复

使用道具 举报

zzh372024750 2022-5-2 16:24:15 | 显示全部楼层
本楼: 👍   100% (1)
 
 
0% (0)   👎
全局: 👍   97% (2994)
 
 
2% (63)    👎
能不能说一个破坏气氛的话。。。

有的时候是懒得写简洁的代码,特别那种 business logic的,之前的东西就简单复制过去,改改名字算了,省脑子

reviewer 也省脑子

评分

参与人数 1大米 +1 收起 理由
14417335 + 1 给你点个赞!

查看全部评分

回复

使用道具 举报

yipinghan 2022-5-2 13:41:50 来自APP | 显示全部楼层
本楼: 👍   100% (1)
 
 
0% (0)   👎
全局: 👍   87% (7)
 
 
12% (1)    👎
顶楼主
总结很用心
回复

使用道具 举报

TerryYu 2022-5-2 22:13:01 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   100% (72)
 
 
0% (0)    👎
谢谢楼主的帖子和分享
回复

使用道具 举报

647HKUS 2022-5-3 07:29:39 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   100% (17)
 
 
0% (0)    👎
感谢楼主分享

评分

参与人数 1大米 +1 收起 理由
甘村励志馒 + 1 给你点个赞!

查看全部评分

回复

使用道具 举报

本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   98% (1480)
 
 
1% (29)    👎
感谢楼主,mark一下
回复

使用道具 举报

 楼主| Chasedream.df 2022-5-6 08:44:46 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   89% (2318)
 
 
10% (283)    👎
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表