转:靠谱的代码和DRY

http://www.cppblog.com/vczh/archive/2014/07/15/207658.html

 

靠谱的代码和DRY

上次有人来要求我写一篇文章谈谈什么代码才是好代码,是谁我已经忘记了,好像是AutoHotkey还是啥的专栏的作者。撇开那些奇怪的条款不谈,靠谱的 代码有一个共同的特点,就是DRY。DRY就是Don't Repeat Yourself,其实已经被人谈了好多年了,但是几乎所有人都会忘记。

什么是DRY(Don't Repeat Yourself) 

DRY 并不是指你不能复制代码这么简单的。不能repeat的其实是信息,不是代码。要分析一段代码里面的什么东西时信息,就跟给物理题做受力分析一样,想每次 都做对其实不太容易。但是一份代码总是要不断的修补的,所以在这之前大家要先做好TDD,也就是Test Driven Development。这里我对自己的要求是覆盖率要高达95%,不管用什么手段,总之95%的代码的输出都要受到检验。当有了足够多的测试做后盾的时 候,不管你以后发生了什么,譬如说你发现你Repeat了什么东西要改,你才能放心大胆的去改。而且从长远的角度来看,做好TDD可以将开发出相同质量的代码的时间缩短到30%左右(这是我自己的经验值) 。

什么是信息

信息这个词不太好用语言下定义,不过我可以举个例子。譬如说你要把一个配置文件里面的字符串按照分隔符分解成几个字符串,你大概就会写出这样的代码:

// name;parent;description
void ReadConfig(const wchar_t* config)
{auto p = wcschr(config, L';');                            // 1if(!p) throw ArgumentException(L"Illegal config string"); // 2DoName(wstring(config, p));                               // 3auto q = wcschr(p + 1, L';');                             // 4if(!q) throw ArgumentException(L"Illegal config string"); // 5DoParent(wstring(p + 1, q);                               // 6auto r = wcschr(q + 1, L';');                             // 7if(r) throw ArgumentException(L"Illegal config string");  // 8DoDescription(q + 1);                                     // 9
}

这段短短的代码重复了多少信息?

  • 分隔符用的是分号(1、4、7)
  • 第二/三个片段的第一个字符位于第一/二个分号的后面(4、6、7、9)
  • 格式检查(2、5、8)
  • 异常内容(2、5、8)

除了DRY以外还有一个问题,就是处理description的方法跟name和parent不一样,因为他后面再也没有分号了。

那这段代码要怎么改呢?有些人可能会想到,那把重复的代码抽取出一个函数就好了:

wstring Parse(const wchar_t& config, bool end)
{auto next = wcschr(config, L';');ArgumentException up(L"Illegal config string");if (next){if (end) throw up;wstring result(config, next);config = next + 1;return result;}else{if (!end) throw up;wstring result(config);config += result.size();return result;}
}// name;parent;description
void ReadConfig(const wchar_t* config)
{DoName(Parse(config, false));DoParent(Parse(config, false));DoDescription(Parse(config, true));
}

是不是看起来还很别扭,好像把代码修改了之后只把事情搞得更乱了,而且就算config对了我们也会创建那个up变量,就仅仅是为了不 重复代码。而且这份代码还散发出了一些不好的味道,因为对于Name、Parent和Description的处理方法还是不能统一,Parse里面针对 end变量的处理看起来也是很重复,但实际上这是无法在这样设计的前提下消除的。所以这个代码也是不好的,充其量只是比第一份代码强一点点。

实 际上,代码之所以要写的好,之所以不能repeat东西,是因为产品狗总是要改需求,不改代码你就要死,改代码你就要加班,所以为了减少修改代码的痛苦, 我们不能repeat任何信息。举个例子,有一天产品狗说,要把分隔符从分号改成空格!一下子就要改两个地方了。description后面要加tag! 这样你处理description的方法又要改了因为他是以空格结尾不是0结尾。

因此针对这个片段,我们需要把它改成这样:

vector<wstring> SplitString(const wchar_t* config, wchar_t delimiter)
{vector<wstring> fragments;while(auto next = wcschr(config, delimiter)){fragments.push_back(wstring(config, next));config = next + 1;}fragments.push_back(wstring(config));return fragments; // C++11就是好!
}void ReadConfig(const wchar_t* config)
{auto fragments = SplitString(config, L';');if(fragments.size() != 3){throw ArgumentException(L"Illegal config string");}DoName(fragments[0]);DoParent(fragments[1]);DoDescription(fragments[2]);
}

我们可以发现,分号(L';')在这里只出现了一次,异常内容也只出现了一次,而且处理name、parent和 description的代码也没有什么区别了,检查错误也更简单了。你在这里还给你的Library增加了一个SplitString函数,说不定在以 后什么地方就用上了,比Parse这种专门的函数要强很多倍。

大家可以发现,在这里重复的东西并不仅仅是复制了代码,而是由于你把 同一个信息散播在了代码的各个部分导致了有很多相近的代码也散播在各个地方,而且还不是那么好通过抽成函数的方法来解决。因为在这种情况下,就算你把重复 的代码抽成了Parse函数,你把函数调用了几次实际上也等于重复了信息。因此正确的方法就是把做事情的方法变一下,写成SplitString。这个 SplitString函数并不是通过把重复的代码简单的抽取成函数而做出来的。去掉重复的信息会让你的代码的结构发生本质的变化

这个问题其实也有很多变体:

  • 不能有Magic Number。L';'出现了很多遍,其实就是个Magic Number。所以我们要给他个名字,譬如说delimiter。
  • 不要复制代码。这个应该不用我讲了。 
  • 解耦要做成正交的。SplitString虽然不是直接冲着读config来写的,但是它反映了一个在其它地方也会遇到的常见的问题。如果用Parse的那个版本,显然只是看起来解决了问题而已,并没有给你带来任何额外的效益。

信息一旦被你repeat了,你的代码就会不同程度的出现各种腐烂或者破窗,上面那三条其实只是我能想到的比较常见的表现形式。这件事情也告诉我们,当高手告诉你什么什么不能做的时候,得想一想背后的原因,不然跟封建迷信有什么区别。

转载于:https://www.cnblogs.com/code-style/p/4053934.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/264221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

omitting directory `folder/'

题记&#xff1a;一个问题&#xff0c;若遇见第一次&#xff0c;不知道如何解决&#xff0c;经查资料后处理掉可以原谅&#xff1b;若遇见第二次&#xff0c;还是一头雾水&#xff01;反省自己&#xff0c;特此备录。 在linux CentOS6.5 系统中复制文件夹时提示如下&#xff1a…

GridView中的CheckBox单击事件(oncheckedchanged)

在GridView中加入 CheckBox控件&#xff0c;想通过单击选中出现如下图所示效果&#xff1a; 具体做法是&#xff1a; 前台GV部份省掉。只加关键的CheckBox部份。 <asp:CheckBox ID"ItemCheckBox" oncheckedchanged"ItemCheckBox_CheckedChanged" AutoPo…

我的世界服务器怎么找到指定路径,[小白]MC服务端目录详解

服务器的前期准备工作都做好了&#xff0c;是时候上传服务端到服务器了。不过……先等等&#xff0c;在上传前你应该先了解一下服务端目录的结构以便于后期的维护&#xff01;如图所示的是MCPC1.6.4的服务端结构示意图&#xff0c;其他开服端基本相同&#xff0c;接下来我将逐一…

第四章:条件语句(if)和循环结构(while)

1.流程控制 含义与作用 Python程序执行&#xff0c;一定按照某种规律在执行 a.宏观一定是自上而下(逻辑上方代码一定比逻辑下方代码先执行)&#xff1a;顺序结构b.遇到需要条件判断选择不同执行路线的执行方式&#xff1a;分支结构c.有些事情需要重复不断的去执行(当满足某种条…

ArcGis开发过程中遇到HRESULT:0x80040213错误

ArcGis开发过程中遇到HRESULT:0x80040213错误&#xff1a;错误原因&#xff1a; COM资源没完全释放。数据量少时&#xff0c;不会出现问题&#xff0c;但是如果是几百个数据&#xff0c;循环调用&#xff0c;肯定会出现该问题。解决方法&#xff1a;释放资源&#xff0c;.Net环…

css样式加入的法方,初学必知:XHTML网页中加入CSS的五种方_css

在Xhtml网页中如何加入css呢&#xff1f;这篇教程告诉大家引入CSS的几种方式。XHTML文件是通过CSS样式进行显示的控制的&#xff0c;也就是结合XHTML与CSS来表现页面内容。那么到底有哪些方式在XHTML文件中引入CSS样式呢&#xff1f;一、使用STYLE属性将STYLE属性直接加在个别的…

有人描述几个歌手的话

许巍洒脱似风&#xff1b;朴树飘逸似云&#xff1b;郑钧孤傲似冰&#xff1b;李健温婉似水&#xff1b;汪峰激昂似火&#xff1b;......转载于:https://www.cnblogs.com/shangge/archive/2009/09/01/1558296.html

java中方法的参数传递机制

转载自 &#xff1a;https://www.cnblogs.com/lixiaolun/p/4311863.html 问&#xff1a;当一个对象被当作参数传递到一个方法后&#xff0c;此方法可改变这个对象的属性&#xff0c;并可返回变化后的结果&#xff0c;那么这里到底是值传递还是引用传递?   答:是值传递。Java…

2009'中国GIS优秀工程公示

2009中国GIS优秀工程公示http://www.gislm.com/showtopic-393-1.aspx#950 转载于:https://www.cnblogs.com/mygis_3/archive/2009/09/02/1558492.html

GIT-Linux(CentOS7)系统部署git服务器

GIT-Linux(CentOS7)系统部署git服务器 root账号登录 一、 安装并配置必要的依赖关系在CentOS系统上安装所需的依赖&#xff1a;ssh&#xff0c;防火墙,postfix(用于邮件通知) &#xff0c;wget&#xff0c;以下这些命令也会打开系统防火墙中的HTTP和SSH端口访问。 1.安装ssh su…

vue 点击渲染ajax,vue中在页面加载时发送ajax请求获取数据渲染不到页面上

ajax是异步执行的。{{bookId}}在setData里面处理数据export default {name: app,data() {bookId : },created(){this.loadData(this.setData);},methods:{loadData(callBack) {const This this;const url https://api.douban.com/v2/book/1220562;getBookPromise().then(res …

2字段添加注释_Tableau学习系列(7):计算字段

计算字段是使用函数和运算符构造公式&#xff0c;对数据源字段&#xff08;包括维度、度量、参数等&#xff09;进行重新定义的字段。1 创建计算字段这里使用自带超市的数据&#xff0c;对各省市销售额从高到低排序进行可视化&#xff0c;但是如果想对各省销售额划份为高、中、…

SourceProvider.getJniDirectories

2019独角兽企业重金招聘Python工程师标准>>> 今天android studio跑项目&#xff0c;build了一下&#xff0c;始终跑不起&#xff0c;把gradle和android studio都更新了一下&#xff0c;还是不行&#xff0c; 一直报这个错误 UnsupportedMethodExceptionFailed to se…

虚拟服务器问题,虚拟主机常见的五大问题

导语&#xff1a;关于虚拟主机&#xff0c;很多用户在使用过程中&#xff0c;会遇到各种各样的问题&#xff0c;根据以往的客户咨询经验&#xff0c;我们总结出了几个比较常见的问题和相应的解决方案&#xff0c;希望能够帮到大家。1.空间的使用情况怎么查看有的时候客户想要看…

MPU6050开发 -- 卡尔曼滤波(转)

MPU6050开发 -- 卡尔曼滤波转载于:https://www.cnblogs.com/LittleTiger/p/10735074.html

文章内容分页

因为程序是C#.net编写的&#xff0c;关于C#如此的例子、资料网上一点也找不到&#xff1b;所以只好从程序结构开始分析&#xff0c;一点程序却用了半天的时候完成&#xff0c;汗颜&#xff01;不足之处&#xff0c;请大家指点&#xff0c;有什么更好的方法请告知。 显示内容部…

ORACLE搭建Stream过程中报错【error收集】

错误一&#xff1a;在配置完源库和目标数据库后&#xff0c;创建复制管理员。连接上复制管理员后&#xff0c;在源库执行MAINTAIN_TABLE过程&#xff1a; declarev_tables DBMS_UTILITY.UNCL_ARRAY; beginv_tables(1) : hr.test01;v_tables(2) : hr.test02;v_tables(3) : hr.te…

元月元日是哪一天_元宵节的农历日期是哪一天 - 中国万年历

摘要元宵节农历时间  2018年03月02日 星期五 (戊戌年(狗年)正月十五)  农历正月十五元宵节&#xff0c;是我国一个重要的传统节日。这一天古代称为“上元”&#xff0c;其夜则称“元夜”、“元夕”或“元宵”。我国古代的历法和月相有密切的关系&#xff0c;每月十五日必逢…

服务器和交换机之间网络协议,网络协议是计算机网络中服务器,计算机,交换机.doc...

网络协议是计算机网络中服务器,计算机,交换机篇一&#xff1a;计算机网络-参考答案(1)不能保证所有题目都在里面&#xff0c;但能保证大部分题目都在这里。(2)建议用快捷方式ctrlf 进行答案的查找&#xff0c;关键字只需复制题目的部分文字即可&#xff0c;这样可以提高速度&am…

5种ASP.NET页面间传递参数实例代码

本文假设第一个页面为send.aspx&#xff0c;第二个页面为receive.aspx 1、通过URL链接地址传递 (1) send.asp代码 protected void Button1_Click(object sender, EventArgs e) { Request.Redirect("Default2.aspx?usernamehonge"); } …