最近关于 Linus Torvalds 的一个采访中,这位 Linux 的创始人,在采访过程中大约 14:20 的时候,提及了关于代码的 “good taste”。good taste?采访者请他展示更多的细节,于是,Linus Torvalds 展示了一张提前准备好的插图。

他展示的是一个代码片段。但这段代码并没有 “good taste”。这是一个具有 “poor taste” 的代码片段,把它作为例子,以提供一些初步的比较。

这是一个用 C 写的函数,作用是删除链表中的一个对象,它包含有 10 行代码。

他把注意力集中在底部的 if 语句。正是这个 if 语句受到他的批判。

我暂停了这段视频,开始研究幻灯片。我发现我最近有写过和这很像的代码。Linus 不就是在说我的代码品味很差吗?我放下自傲,继续观看视频。

随后, Linus 向观众解释,正如我们所知道的,当从链表中删除一个对象时,需要考虑两种可能的情况。当所需删除的对象位于链表的表头时,删除过程和位于链表中间的情况不同。这就是这个 if 语句具有 “poor taste” 的原因。

但既然他承认考虑这两种不同的情况是必要的,那为什么像上面那样写如此糟糕呢?

接下来,他又向观众展示了第二张幻灯片。这个幻灯片展示的是实现同样功能的一个函数,但这段代码具有 “goog taste” 。

原先的 10 行代码现在减少为 4 行。

但代码的行数并不重要,关键是 if 语句,它不见了,因为不再需要了。代码已经被重构,所以,不用管对象在列表中的位置,都可以运用同样的操作把它删除。

Linus 解释了一下新的代码,它消除了边缘情况,就是这样。然后采访转入了下一个话题。

我琢磨了一会这段代码。 Linus 是对的,的确,第二个函数更好。如果这是一个确定代码具有 “good taste” 还是 “bad taste” 的测试,那么很遗憾,我失败了。我从未想到过有可能能够去除条件语句。我写过不止一次这样的 if 语句,因为我经常使用链表。

这个例子的意义,不仅仅是教给了我们一个从链表中删除对象的更好方法,而是启发了我们去思考自己写的代码。你通过程序实现的一个简单算法,可能还有改进的空间,只是你从来没有考虑过。

以这种方式,我回去审查最近正在做的项目的代码。也许是一个巧合,刚好也是用 C 写的。

我尽最大的能力去审查代码,“good taste” 的一个基本要求是关于边缘情况的消除方法,通常我们会使用条件语句来消除边缘情况。你的测试使用的条件语句越少,你的代码就会有更好的 “taste” 。

下面,我将分享一个通过审查代码进行了改进的一个特殊例子。

这是一个关于初始化网格边缘的算法。

下面所写的是一个用来初始化网格边缘的算法,网格 grid 以一个二维数组表示:grid[行][列] 。

再次说明,这段代码的目的只是用来初始化位于 grid 边缘的点的值,所以,只需要给最上方一行、最下方一行、最左边一列以及最右边一列赋值即可。

为了完成这件事,我通过循环遍历 grid 中的每一个点,然后使用条件语句来测试该点是否位于边缘。代码看起来就是下面这样:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (r = 0; r < GRID_SIZE; ++r) {
    for (c = 0; c < GRID_SIZE; ++c) {
        // Top Edge
        if (r == 0)
            grid[r][c] = 0;
        // Left Edge
        if (c == 0)
            grid[r][c] = 0;
        // Right Edge
        if (c == GRID_SIZE - 1)
            grid[r][c] = 0;
        // Bottom Edge
        if (r == GRID_SIZE - 1)
            grid[r][c] = 0;
    }
}

虽然这样做是对的,但回过头来看,这个结构存在一些问题。

  1. 复杂性 — 在双层循环里面使用 4 个条件语句似乎过于复杂。
  2. 高效性 — 假设 GRID_SIZE 的值为 64,那么这个循环需要执行 4096 次,但需要进行赋值的只有位于边缘的 256 个点。

用 Linus 的眼光来看,将会认为这段代码没有 “good taste” 。

所以,我对上面的问题进行了一下思考。经过一番思考,我把复杂度减少为包含四个条件语句的单层 for 循环。虽然只是稍微改进了一下复杂性,但在性能上也有了极大的提高,因为它只是沿着边缘的点进行了 256 次循环。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (i = 0; i < GRID_SIZE * 4; ++i) {
    // Top Edge
    if (i < GRID_SIZE)
        grid[0][i] = 0;
    // Right Edge
    else if (i < GRID_SIZE * 2)
        grid[i - GRID_SIZE][GRID_SIZE - 1] = 0;
    // Left Edge
    else if (i < GRID_SIZE * 3)
        grid[i - (GRID_SIZE * 2)][0] = 0;
    // Bottom Edge
    else
        grid[GRID_SIZE - 1][i - (GRID_SIZE * 3)] = 0;
}

的确是一个很大的提高。但是它看起来很丑,并不是易于阅读理解的代码。基于这一点,我并不满意。

我继续思考,是否可以进一步改进呢?事实上,答案是 YES!最后,我想出了一个非常简单且优雅的算法,老实说,我不敢相信我会花了那么长时间才发现这个算法。

下面是这段代码的最后版本。它只有一层 for 循环并且没有条件语句。另外。循环只执行了 64 次迭代,极大的改善了复杂性和高效性。

 
 
1
2
3
4
5
6
7
8
9
10
for (i = 0; i < GRID_SIZE; ++i) {
    // Top Edge
    grid[0][i] = 0;
    // Bottom Edge
    grid[GRID_SIZE - 1][i] = 0;
    // Left Edge
    grid[i][0] = 0;
    // Right Edge
    grid[i][GRID_SIZE - 1] = 0;
}

这段代码通过每次循环迭代来初始化四条边缘上的点。它并不复杂,而且非常高效,易于阅读。和原始的版本,甚至是第二个版本相比,都有天壤之别。

至此,我已经非常满意了。

那么,我是一个有 “good taste” 的开发者么?

我觉得我是,但是这并不是因为我上面提供的这个例子,也不是因为我在这篇文章中没有提到的其它代码……而是因为具有 “good taste” 的编码工作远非一段代码所能代表。Linus 自己也说他所提供的这段代码不足以表达他的观点。

我明白 Linus 的意思,也明白那些具有 “good taste” 的程序员虽各有不同,但是他们都是会将他们之前开发的代码花费时间重构的人。他们明确界定了所开发的组件的边界,以及是如何与其它组件之间的交互。他们试着确保每一样工作都完美、优雅。

其结果就是类似于 Linus 的 “good taste” 的例子,或者像我的例子一样,不过是千千万万个 “good taste”。

你会让你的下个项目也具有这种 “good taste” 吗?

SAMPLE:

1.env.sh

####

#!/bin/bash
# config all schema env in this file

#define UAT
export NLS_LANG=AMERICAN_AMERICA.UTF8
export ENVS=/xpruatdb/change/env/env_xpruat.sql
export SCHEMA_HOME=/xpruatdb/change/schema/20170515.2

#define PROD
#export NLS_LANG=AMERICAN_AMERICA.UTF8
#export ENVS=/edrproddb/change/env/env_edrprod.sql
#export SCHEMA_HOME=/edrproddb/change/schema/2016_05_10_2016.2

##devfine version
export V1=REL-007-08-000
export V2=REL-007-08-005
export V3=REL-007-08-015
export V4=REL-007-10-000
export V5=REL-007-10-005
export V6=REL-007-10-010

echo $ENV

###define details running path

case $ENV in
rollout)
export SCHEMA_HOME_1=$SCHEMA_HOME/$V1/xpr/schema_changes/xpr/rollout/
export SCHEMA_HOME_2=$SCHEMA_HOME/$V2/xpr/schema_changes/xpr/rollout/
export SCHEMA_HOME_3=$SCHEMA_HOME/$V3/xpr/schema_changes/xpr/rollout/
export SCHEMA_HOME_4=$SCHEMA_HOME/$V4/xpr/schema_changes/xpr/rollout/
export SCHEMA_HOME_5=$SCHEMA_HOME/$V5/xpr/schema_changes/xpr/rollout/
export SCHEMA_HOME_6=$SCHEMA_HOME/$V6/xpr/schema_changes/xpr/rollout/

echo $ENVS

echo r
;;

prepare)
# Define prepare details path

export SCHEMA_HOME_1=$SCHEMA_HOME/$V1/xpr/schema_changes/xpr/prepare/
export SCHEMA_HOME_2=$SCHEMA_HOME/$V2/xpr/schema_changes/xpr/prepare/
export SCHEMA_HOME_3=$SCHEMA_HOME/$V3/xpr/schema_changes/xpr/prepare/
export SCHEMA_HOME_4=$SCHEMA_HOME/$V4/xpr/schema_changes/xpr/prepare/
export SCHEMA_HOME_5=$SCHEMA_HOME/$V5/xpr/schema_changes/xpr/prepare/
export SCHEMA_HOME_6=$SCHEMA_HOME/$V6/xpr/schema_changes/xpr/prepare/

echo p
;;

regress)
# define regress details path

export SCHEMA_HOME_1=$SCHEMA_HOME/$V1/xpr/schema_changes/xpr/regress/
export SCHEMA_HOME_2=$SCHEMA_HOME/$V2/xpr/schema_changes/xpr/regress/
export SCHEMA_HOME_3=$SCHEMA_HOME/$V3/xpr/schema_changes/xpr/regress/
export SCHEMA_HOME_4=$SCHEMA_HOME/$V4/xpr/schema_changes/xpr/regress/
export SCHEMA_HOME_5=$SCHEMA_HOME/$V5/xpr/schema_changes/xpr/regress/
export SCHEMA_HOME_6=$SCHEMA_HOME/$V6/xpr/schema_changes/xpr/regress/

echo re
;;
*) echo 'please use right option'
;;
esac

2. apply_schema_change.sh

###define it is for rollout

export ENV=rollout
echo $ENV
. env.sh

echo h
echo $ENVS

#####################################
# Check DB connection is correct
#####################################
sqlplus /nolog <<EOF
@${ENVS}
connect &v_system_un/&v_system_pw@&v_conn_str
show user
prompt &v_conn_str
set pages 1000
select * from v\$instance;
exit
EOF

############################################
# check invalid objects
############################################
cd $SCHEMA_HOME
sqlplus /nolog << EOF
@${ENVS}
connect &v_system_un/&v_system_pw@&v_conn_str
set pages 1000
set lines 134
col owner for a12
col object_name for a35
col object_type for a10
col last_ddl_time for a15
set echo on
alter session set nls_date_format = 'DD-MON-YY HH24:MI';
spool invalid_object_before_appl.lst
select owner,object_name,object_type,last_ddl_time
from dba_objects where status = 'INVALID'
order by owner, object_name
/
spool off
exit
EOF

#####################################
# Main Program
#####################################

####runing 6 version

for i in 1 2 3 4 5 6

do
#############################################
echo schema change SCHEMA_HOME_$i
banner xpr $i
#############################################

date
echo Press any key to continue
read ANS

## use eval to cd file_location
des=$`echo SCHEMA_HOME_$i`
eval cd $des
apply_schema_change.sh ${ENVS}
date
echo done xpr $i
eval echo $des
echo Press any key to continue
read ANS
echo done $0 !

done

#############################################
cd $SCHEMA_HOME

sqlplus /nolog << EOF
rem Grant
@${ENVS}
conn &v_xprdata_un/&v_xprdata_pw@&v_conn_str
@gen_grant &v_xprusr_un
@gen_grant &v_xprpatch_un
@gen_grant_select &v_xprquery_un

disconnect

rem Create synonyms
@${ENVS}

@gen_and_create_syn &v_xprusr_un &v_xprusr_pw &v_conn_str &v_xprdata_un

@gen_and_create_syn &v_xprpatch_un &v_xprpatch_pw &v_conn_str &v_xprdata_un

@gen_and_create_syn &v_xprquery_un &v_xprquery_pw &v_conn_str &v_xprdata_un

EOF

############################################
# check invalid objects
############################################
cd $SCHEMA_HOME
sqlplus /nolog << EOF
@${ENVS}
connect &v_system_un/&v_system_pw@&v_conn_str
set pages 1000
set lines 134
col owner for a12
col object_name for a35
col object_type for a10
col last_ddl_time for a15
set echo on
alter session set nls_date_format = 'DD-MON-YY HH24:MI';
spool invalid_object_after_appl.lst
select owner,object_name,object_type,last_ddl_time
from dba_objects where status = 'INVALID'
order by owner, object_name
/
spool off
exit
EOF

向Linus学习,让代码具有good taste的更多相关文章

  1. 20145314郑凯杰 《Java程序设计》第2周学习总结 代码开始!

    ---恢复内容开始--- 20145314郑凯杰 <Java程序设计>第2周学习总结 代码开始! 教材学习内容总结 跟着教材的顺序开始总结我学过的内容: 1编辑.编译.运行教材上代码 这部 ...

  2. C++入职学习篇--代码规范(持续更新)

    C++入职学习篇--代码规范(持续更新) 一.头文件规范 在头文件中大家一般会定义宏.引入库函数.声明.定义全局变量等,在设计时最后进行分类,代码示范(自己瞎琢磨的,请多多指点): #ifndef T ...

  3. Guava 已经学习的代码整理

    Guava 已经学习的代码整理 Guava 依赖: compile group: 'com.google.guava', name: 'guava', version: '18.0' 以下是我自己在开 ...

  4. CTR学习笔记&代码实现2-深度ctr模型 MLP->Wide&Deep

    背景 这一篇我们从基础的深度ctr模型谈起.我很喜欢Wide&Deep的框架感觉之后很多改进都可以纳入这个框架中.Wide负责样本中出现的频繁项挖掘,Deep负责样本中未出现的特征泛化.而后续 ...

  5. CTR学习笔记&代码实现3-深度ctr模型 FNN->PNN->DeepFM

    这一节我们总结FM三兄弟FNN/PNN/DeepFM,由远及近,从最初把FM得到的隐向量和权重作为神经网络输入的FNN,到把向量内/外积从预训练直接迁移到神经网络中的PNN,再到参考wide& ...

  6. CTR学习笔记&代码实现4-深度ctr模型 NFM/AFM

    这一节我们总结FM另外两个远亲NFM,AFM.NFM和AFM都是针对Wide&Deep 中Deep部分的改造.上一章PNN用到了向量内积外积来提取特征交互信息,总共向量乘积就这几种,这不NFM ...

  7. CTR学习笔记&代码实现5-深度ctr模型 DeepCrossing -> DCN

    之前总结了PNN,NFM,AFM这类两两向量乘积的方式,这一节我们换新的思路来看特征交互.DeepCrossing是最早在CTR模型中使用ResNet的前辈,DCN在ResNet上进一步创新,为高阶特 ...

  8. CTR学习笔记&代码实现6-深度ctr模型 后浪 xDeepFM/FiBiNET

    xDeepFM用改良的DCN替代了DeepFM的FM部分来学习组合特征信息,而FiBiNET则是应用SENET加入了特征权重比NFM,AFM更进了一步.在看两个model前建议对DeepFM, Dee ...

  9. Ruby学习之代码块

    代码块在其他的语言中都或多或少接触过一些,如perl中sort{$a<=>$b}keys,传入代码块实现按数值排序,在swift中用到闭包,更加深入学习到training closure. ...

随机推荐

  1. HTML5调用传感器的资料汇总

    都可以调用:devicetemperature(温度).devicepressure(压力).devicehumidity(湿度).devicelight(光).devicenoise(声音).dev ...

  2. cogs——619. [金陵中学2007] 传话

    619. [金陵中学2007] 传话 ★★   输入文件:messagez.in   输出文件:messagez.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 兴趣小 ...

  3. 洛谷 P1023 税收与补贴问题

    P1023 税收与补贴问题 题目背景 每样商品的价格越低,其销量就会相应增大.现已知某种商品的成本及其在若干价位上的销量(产品不会低于成本销售),并假设相邻价位间销量的变化是线性的且在价格高于给定的最 ...

  4. 手把手教你编写一个简单的PHP模块形态的后门

    看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web ...

  5. MongoDB升级导致启动失败

    起因 最近项目使用MongoDB,但是作为一个技术菜鸟,NoSQL数据库我还真不会用,于是我就在自己的阿里云服务器上安装了一个MongoDB4.0.9. 现象 但是当我使用yum -y update升 ...

  6. CodeIgniter RestServer中put请求获取不到參数的问题解决

    近期用restserver遇到个蛋疼的问题,发现$this->put得到的參数都是null.查了一下发现.这貌似这个普遍问题,參见链接:https://github.com/chriskacer ...

  7. MySQL-导入与导出

    CSV文件导入MySQL LOAD DATA INFILE语句允许您从文本文件读取数据,并将文件的数据快速导入数据库的表中. 导入文件操作之前,需要准备以下内容: 一.将要导入文件的数据对应的数据库表 ...

  8. C# oracle 参数传递的多种方式 留着复习

    ORA-01036 非法的变量名/编号,(解决) 博客分类: oracle SQL  下边的代码就会造成  ORA-01036 非法的变量名/编号 cmd.CommandText = "SE ...

  9. 如何装载Storyboard中的ViewController?

      如上图所示,如何装载Storyboard中指定的ViewController? 首先,需要指定ViewController的ID,如上图右上方红色方框内的Storyboard ID.然后使用下面的 ...

  10. python-----利用filecmp删除重复文件

    以下代码素材自取:链接:https://pan.baidu.com/s/1fL17RjKyGjpvpeeUFONCaQ  提取码:zgiw # coding:utf-8 import os impor ...