转:靠谱的代码和DRY
http://www.cppblog.com/vczh/archive/2014/07/15/207658.html
上次有人来要求我写一篇文章谈谈什么代码才是好代码,是谁我已经忘记了,好像是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';'); //
if(!p) throw ArgumentException(L"Illegal config string"); //
DoName(wstring(config, p)); //
auto q = wcschr(p + , L';'); //
if(!q) throw ArgumentException(L"Illegal config string"); //
DoParent(wstring(p + , q); //
auto r = wcschr(q + , L';'); //
if(r) throw ArgumentException(L"Illegal config string"); //
DoDescription(q + ); //
}
这段短短的代码重复了多少信息?
- 分隔符用的是分号(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 + ;
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 + ;
}
fragments.push_back(wstring(config));
return fragments; // C++11就是好!
} void ReadConfig(const wchar_t* config)
{
auto fragments = SplitString(config, L';');
if(fragments.size() != )
{
throw ArgumentException(L"Illegal config string");
}
DoName(fragments[]);
DoParent(fragments[]);
DoDescription(fragments[]);
}
我们可以发现,分号(L';')在这里只出现了一次,异常内容也只出现了一次,而且处理name、parent和 description的代码也没有什么区别了,检查错误也更简单了。你在这里还给你的Library增加了一个SplitString函数,说不定在以 后什么地方就用上了,比Parse这种专门的函数要强很多倍。
大家可以发现,在这里重复的东西并不仅仅是复制了代码,而是由于你把 同一个信息散播在了代码的各个部分导致了有很多相近的代码也散播在各个地方,而且还不是那么好通过抽成函数的方法来解决。因为在这种情况下,就算你把重复 的代码抽成了Parse函数,你把函数调用了几次实际上也等于重复了信息。因此正确的方法就是把做事情的方法变一下,写成SplitString。这个 SplitString函数并不是通过把重复的代码简单的抽取成函数而做出来的。去掉重复的信息会让你的代码的结构发生本质的变化。
这个问题其实也有很多变体:
- 不能有Magic Number。L';'出现了很多遍,其实就是个Magic Number。所以我们要给他个名字,譬如说delimiter。
- 不要复制代码。这个应该不用我讲了。
- 解耦要做成正交的。SplitString虽然不是直接冲着读config来写的,但是它反映了一个在其它地方也会遇到的常见的问题。如果用Parse的那个版本,显然只是看起来解决了问题而已,并没有给你带来任何额外的效益。
信息一旦被你repeat了,你的代码就会不同程度的出现各种腐烂或者破窗,上面那三条其实只是我能想到的比较常见的表现形式。这件事情也告诉我们,当高手告诉你什么什么不能做的时候,得想一想背后的原因,不然跟封建迷信有什么区别。
转:靠谱的代码和DRY的更多相关文章
- Strategy 设计模式 策略模式 超靠谱原代码讲解
先来假设一种情,我们需要向三种不同的客户做出不同的报价,一般来说要肿么设计呢,是不是马上会想到用IF,没有错,对于这种情况,策略模式是最好的选.大家可以这么理解,如果有情况需要用到大量的IF,那你用策 ...
- DRY(Don't Repeat Yourself )原则
凡是写过一些代码的程序猿都能够意识到应该避免重复的代码和逻辑.我们通过提取方法,提取抽象类等等措施来达到这一目的.我们总能时不时的听到类似这样的话:”把这些公用的类放到shared项目去,别的项目还要 ...
- DRY
DRY(Don't Repeat Yourself )原则 凡是写过一些代码的程序猿都能够意识到应该避免重复的代码和逻辑.我们通过提取方法,提取抽象类等等措施来达到这一目的.我们总能时不时的听到类 ...
- 代码管理必备-----git使用上传码云
作为一个程序员,你要学会代码的管理,这是一个最基本的修养,就像是一个剑客的剑谱,代码管理,目前流行的是svn和git,但是很不好的是git如果没有插件的话,很多人都不会用git bash 来实现自己的 ...
- 【译】最大限度地降低多线程 C# 代码的复杂性
分支或多线程编程是编程时最难最对的事情之一.这是由于它们的并行性质所致,即要求采用与使用单线程的线性编程完全不同的思维模式.对于这个问题,恰当类比就是抛接杂耍表演者,必须在空中抛接多个球,而不要让它们 ...
- 程序设计中的dry原则
DRY:dont repeat yourself 假设一个逻辑(代码块)会重复两次或者以上,应该写成函数被调用 为什么呢,实际上,我们处处可见重复性的代码.这除了增加工作量之外,还会增加维护难度. d ...
- Lilypond+TexLive(LuaLatex+lyluatex)+VS Code实现谱文混排
没想到发文章反而更难被预览了,那就复制一份到随笔里好了. 多次尝试之下,终于实现了现阶段谱文混排的最理想方式: 1. 综合Latex的排版(还有广泛适用人群)的优势以及Lilypond的美观优势: 2 ...
- 理解 JavaScript 回调函数并使用
JavaScript中,函数是一等(first-class)对象:也就是说,函数是 Object 类型并且可以像其他一等对象(String,Array,Number等)一样使用.它们可以"保 ...
- Django框架
一.首先,到底什么是框架? 想要回答这个问题,我们要慢慢来. ①首先从DRY原则开始说起 Don't Repeat Yourself,不要重复你的代码. DRY原则的重要性怎么提都不过分,很多人说编程 ...
随机推荐
- mybatis03
.2导入jar包 从mybatis管网下载(地址:https://github.com/mybatis/mybatis-3/releases) mybatis-.pdf---操作手册 mybatis- ...
- java反射性能
项目中用到了java的反射,可以大大减少代码量.但是反射的性能却不容乐观,做了个简单的测试,如下. public void noreflect() { Person p = new Person(); ...
- iOS 开发之 ReactiveCocoa(基础)
前言 前段时间在看Masonry这个全新的第三方的布局框架的时候,开始了解了链式编程.后来慢慢的又开始了解函数式编程和响应式编程.在这集中的编程思想下,开始接触和研究了ReactiveCocoa这个框 ...
- EF 命令
我们选VS中工具-库程序包管理器-程序包管理器控制台, 这时在VS底部会出现控制台 这时候我们要执行四个操作: 下载安装EF 到工程.PM> Install-Package EntityFram ...
- System Operations on AWS - Lab 4W - Monitoring (Windows)
创建Web Server实例,配置CloudWatch来收集Web Server的系统日志,当错误登录次数达到设定值时触发报警 1. 创建Web Server 1.1 创建一个IAM策略 1.2 创建 ...
- Django 初探--Django的开发服务器及创建数据库(笔记)
1.Django的开发服务器 Django框架中包含一些轻量级的web应用服务器,开发web项目时不需再对其配置服务器,Django提供的内置服务器可以在代码修改时自动加载,从而实现网站的迅速开发. ...
- UESTCOJ-BiliBili, ACFun… And More!(水题)
BiliBili, ACFun… And More! Time Limit: 3000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Ja ...
- 使用openrowset跨库查询
--insert fj_studentinfo--select *--from-- openrowset('SQLOLEDB','localhost';'sa';'password',dbname. ...
- NodeJS学习笔记—2.AMD规范
CommonJS加载模块是同步的,而AMD模块加在是非同步的,允许指定回调函数.由于Nodejs主要用于服务器编程,模块文件一般都存在于本地,所以加载很快,不需要考虑非同步加载,用CommonJS即可 ...
- CSS 导航栏
实例: 导航栏 Home News Articles Forum Contact About 导航栏 熟练使用导航栏,对于任何网站都非常重要. 使用CSS你可以转换成好看的导航栏而不是枯燥的HTML菜 ...