一文详解|如何写出优雅的代码
一、好代码的定义
关于好代码的定义,各路大神都给出了自己的定义和见解
整洁的代码如同优美的散文。—— Grady Booch
任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。—— Martin Fowler
首先要达成一致,我们写的代码,除了用于机器执行产生我们预期的效果之外,更多的时候是给人读的,可能是后续的维护人员,更多时候是一段时间后的作者本人,因此优雅面向不同的用户有两层含义的解读。
1.对人而言,代码的整洁,清晰的逻辑;
2.对机器而言,准确性、执行性能、异常处理机制等;
二、代码整洁
1. 有意义的命名
public List<int[]> getItem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x: theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
整体逻辑没啥问题,读完之后,就有很多问题在脑海中产生
1. theList中是存储什么东西的数组?
2. theList第一个值是做什么的?
3. 值4的意义又是什么?
4. 返回的列表该怎么使用?
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}
2. 优雅的注释
1. 一些被注释掉的代码
//something code
//something code
2. 位置标记
//begin
someting code;
//end
3. 签名标记
/** add by xiaoli*/
4. 非公用方法的javadoc
/**
* doSomething
*/
private void doSomething(){
}
5. 日志式注释
/** add xx
* update sometimes
* update sometimes
* update sometimes
*/
6. 误导性注释
//此处怎样xx
3. 优雅的函数
3.1 务必要短小
public String renderPageWithSetupAndTeardowns(Page page, boolean isSuite) throws Exception{
if(isTestPage(page)){
includeSetupAndTeardownPages(page,isSuite);
}
return page.getHtml();
}
3.2 只做一件事
3.3 抽象层级一致
//把大象装进冰箱
public void frozenElephant(){
//1. 捕捉大象
//2. 运输大象
//3. 打开冰箱
//4. 放入大象
//5. 关闭冰箱
}
public void frozenElephant(){
//1. 捕捉大象
catchElephant();
//2. 运输大象
transportElephant();
//将大象放入冰箱
putElephantInRefrigerator();
}
public void catchElephant(){
}
public void transportElephant(){
}
public void putElephantInRefrigerator(){
//打开冰箱
//放入大象
//关闭冰箱
}
if(deletePage() == OK){
if(registry.deleteReference(page.name) == OK){
if(configKeys.deleteKey(page.name.makeKey) == OK){
logger.log("page deleted")
}else{
logger.log("configKey not deleted")
}
}else{
logger.log("deleteReference from registry failed")
}
}else{
logger.log("delete failed")
return Error;
}
try{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey);
}catch(Exception e){
logger.log(e.getMessage());
}
3.5 使用第三方库
三、代码重构
1. 识别代码的坏味道
1.1 重复的代码
1.最单纯的重复代码就是“同一个类的两个函数含有相同的表达式”。这时候需要做的就是采用提炼函数提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码
2.如果重复代码只是相似而不是完全相同,需要先尝试用移动语句重组代码顺序,把相似的部分放在一起以便提炼。
1.2 过长的函数
遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名,可以对一组甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,就要毫不犹豫地那样做,关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。
1.百分之九十九的场合里,要把函数变短,只需使用提炼函数。找到函数中适合集中在一起的部分,将它们提炼出来形成一个新函数。
2.如果函数内有大量的参数和临时变量,最终就会把许多参数传递给被提炼出来的新函数,导致可读性几乎没有任何提升。此时可以经常运用以查询取代临时变量来消除这些临时元素。引入参数对象和保持对象完整则可以将过长的参数列表变得更简洁一些。
1.3 数据的可变性
对数据的修改经常导致出乎意料的结果和难以发现的bug。在一处更新数据,却没有意识到软件中的另一处期望着完全不同的数据,于是出现难以预料的bug,往往比较难排查(需要排查数据流转的整体链路),这就需要一些方法用于约束对数据的更新,降低数据可变性的风险。
1.可以用封装变量来确保所有数据更新操作都通过很少几个函数来进行,使其更容易统一监控和演进
2.如果一个变量在不同时候被用于存储不同的东西, 可以使用拆分变量将其拆分为各自不同用途的变量,从而避免危险的更新操作。
1.4 模块单一职责
所谓模块化,就是力求将代码分出区域,最大化区域内部的交互、最小化跨区域的交互。但是经常出现一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于与所处模块内部的交流,这就是模块功能不单一的典型情况。
1.总看到某个函数为了计算某个值,从另一个对象那儿调用半打的取值函数。如果这个函数需要跟这些数据待在一起,那就使用移动功能把它移过去。
2.一个函数往往会用到几个模块的功能,那么它究竟该被置于何处呢?原则是:判断哪个模块拥有的此函数使用的数据最多,然后就把这个函数和那些数据摆在一起。 如果先以提炼函数将这个函数分解为数个较小的函数并分别置放于不同类中,上面的步骤就会比较容易完成。
2. 函数重构的方法
2.1 Extract Method
2.2 Inline Method
2.3 Replace Temp with Query
2.4 Inline Temp
2.5 Introduce Explaining Variable
2.6 Split Temporary Variable
2.7 Remove Assignments to Parameters
2.8 Replace Method with Method Object
四、小结
《开发者评测局》云原生Serverless专场直播重磅来袭!
12月20日19:00,阿里巴巴研究员、阿里云云原生应用平台总经理丁宇带领Serverless团队与业内大咖共同探讨Serverless未来趋势和产品发展新方向。快来直播间围观吧!
点击阅读原文查看详情。
微信扫码关注该文公众号作者