如何写代码——编程内功⼼法
怎样写好代码 —— 编程独家⼼法
写代码就是学⼀门语⾔然后开始撸代码吗?看完了我的《GoF设计模式》系列⽂章的同学或者本⾝已经就是⽼鸟的同学显然不会这么认为。编程是⼀项⾮常严谨的⼯作!虽然我们⾃嘲为码农,但是这⼯作毕竟不是真正的搬砖,我们是软件⼯程师。编程需要关注的问题太多,不仅仅有语⾔,还有算法、数据结构、编程技巧、编码风格、设计、架构、⼯程化、开发⼯具、团队协作等⽅⽅⾯⾯,涉及到很多层⾯的问题。本⽂将分享⼀下根据我这⼏年来的编程经验总结出的⼀些关于如何写代码的个⼈见解。
由于“跟我混”的⼀些⼩伙伴编程功底相对来说⽐较薄弱,所以在此总结⼀篇“编程内功⼼法”帮助他们渡过职业⽣涯的第⼀个瓶颈期。顺便,也造福⼀下路过的有缘的同学!于是有了此⽂。
前⾔
⾸先,思考⼀个问题,何谓编程?编程就是写代码吗?
所谓的编程,其实就是不断的对这个现实世界中的问题建⽴模型并将其固化为代码⾃动化执⾏的过程。
~ Bug辉 《GoF设计模式 - 解释器模式》
在对问题建⽴模型的过程中,我们会遇到⾮常多不同层⾯的问题,所以我们需要很多领域的知识去解决这些问题。
我们需要管理被操作的数据,因为数据与数据之前是相互有关联的。将数据结构化,通常是编程的第⼀步。关于结构化数据的相关理论以及实践,需要有⼀个专门的学科分⽀或者说课题去研究——数据结构。
我们需要解决⼀个具体的问题,这个具体的问题如何⼀步步去解决,过程是怎么样⼦的——算法。
我们需要将解决⽅案进⾏⾃动化,并以代码的形式进⾏交付——编程语⾔。
如果将⼀个抽象的模型进⾏编码实现,如何实现“这个功能”,如何实现“那个功能”——编程技巧。
问题的规模⼤了,众多代码糅合在⼀起,连程序员⾃⼰都看不懂了!怎么来拆分、模块化这些代码——设计。
代码量已经到了⼀个⼈⽆法完成的地步了,需要团队分⼯合作才能完成了——⼯程化。
你写的代码我看不懂,没法调⽤或者很难调⽤,我写的代码你也看不懂,或者很难看懂。还怎么愉快的玩耍——编码风格/编码规范。
问题的规模继续扩⼤,到了系统⼯程的规模了,之前学的套路已经不管⽤了!怎么来构建这个系统才能实现正确、安全、⾼性能、⾼可⽤——架构。
然⽽这些也只是⼀个系统⼯程中的冰⼭⼀⾓!这是⼀个庞⼤的体系。也正是因为软件开发需要考虑到的问题太多且团队成员⽔品参差不齐,所以团队开发中并不是每个程序员做的事情都是⼀样的。每个⼈都有⾃⼰的⾓⾊、初级⼯程师、中级⼯程师、⾼级⼯程师、架构师、CTO。。。
所以编程不仅仅只是堆砌代码!
说到这⾥,我想起来了⼀件事情——为啥业界普遍鄙视培训出来半道出家的新⼈?⼈与⼈的区别是很⼤的!我见过培训出来也很⽜的。其实,说到底,被鄙视的并不是所有⼈。⽽是那些培训了⼏个⽉之后发现随便个⼯作也能拿“⾼薪”然后还⾃认为编程很简单的新⼈。因为这种经历给了他们⼀种错觉——编程如此简单,我培训⼏个⽉也会嘛!有点像刚学会开车的新司机,很嚣张的对⽼司机说“开车很简单嘛!你看我也会啊!”。语⾔和开发⼯具只是招式,这是外功。⽽编程思想、经验是内功。这些内功并不是靠短短⼏个⽉的培训能够掌握的,这⼀点有点像中国制造业和⽇本制造业的区别。动不动赶英超美可不好。。。
编程并不简单!这是⼀件很严肃的事情。不过今天,我没有办法介绍完所有的⽅⾯!或者说,到今天为⽌,我也并没能掌握所有领域的知识。所以今天我只是分享⼀些关于编码本⾝的⼀些经验。
另外,本⽂主要分享如何写代码,并不是如何⽤Java写代码。所以⽂章中各种语⾔都有可能出现。
怎样写代码 自己做编程编码风格
先来⼀个圈内的段⼦。
⼤部分程序员在⼯作中都很讨厌这四件事情:
1. 写注释
2. 写⽂档
3. 别⼈不写注释
4. 别⼈不写⽂档
o(∩_∩)o 哈哈。。中了没!
这个段⼦其实反映出来⼀个问题,即⼤部分代码都需要通过⼤量注释和⽂档来说明才能将意图传达给维护这些代码的程序员!然⽽,就像上⾯的段⼦说的那样,写⼤量的注释和⽂档其实是⼀件很⿇烦的事情。所以很多时候,由于嫌⿇烦,注释和⽂档就没写,导致维护代码的⼈相当的痛苦。这个苦同学
们肯定都是体会过的!相当于给你个精密仪器要你维护还不给说明书。
其实,打破上⾯那个段⼦描述的那个怪圈的⼀个很有效的⼿段就是统⼀编码风格。优秀的代码可以实现代码即注释,代码本⾝就可以⾮常清晰的体现出它的意图来,让别⼈可以很容易读懂。这就是所谓的可读性!
命名
计算机科学领域中最难的两件事是命名和缓存失效!命名并不简单,很复杂。好的名字可以见名知意,⾮常容易理解。之所以说命名难是因为命名的过程同时也是概念提取的过程!对问题建⽴模型,需要提取概念并赋予其“术语”。这个过程其实是“万⾥长征”中最难的⼀步。毕竟,设计也好,架构也罢,都有成熟的套路可以参考。唯独这个过程,是需要程序设计者⾃⼰进⾏充分的思考的创造性⼯作!
以下是总结出来的⼀些命名经验:
⼀个类是某物、某事、某⼈的抽象,是数据与⾏为的集合体。这恰好符合名词的定义,因此 类名 是⼀个名词!
⽅法名 或者说 函数名 是某操作或者某过程的抽象,是⼀个动作。这恰好符合动词的定义,因此函数
名通常是⼀个动词。
变量名宁可长⼀些说明清楚⽤途也不要⽤a、b、c之类的⽆意义的名称,除⾮是循环计数器中⽤i、j、k等约定俗成的⼀些变量名。⽐如pageIndex和pageSize就要⽐取名成i和s好!取成这种和⽤混淆器混淆过后的代码⼀样的名称没有什么好处,如果算法⽐较复杂的话,过⼀段时间恐怕⾃⼰都会看不懂。
变量名最好包含变量本⾝的业务含义。⽐如ListstudentList = new ArrayList<>();就⽐Listlist = new ArrayList<>();好很多。如果同⼀段代码⾥再出现⼀个List的话,这样就可以很⽅便的取名为teacherList或者teachers⽽不是list1和list2这样的毫⽆意义的名称!
英⽂不好怎么办
这个问题怎么说呢。。
作为⼀名程序员吧,基础的英⽂还是要懂的。要不然发展也容易遇到天花板,学不好编程的。毕竟,最新的技术、解决⽅案、⼯具都是从国外传过来的。如果是解决⼀些基础性的问题,每天只做做CRUD,好像英⽂确实不怎么⽤得上。但是⼀旦遇到⼀些实质性问题,恐怕只能到英⽂⽹站上喽!ㄟ(▔ ,▔)ㄏ 不要跟我说你编程可以不需要Stack Overflow。copying and pasting from stackoverflow 可是终极编程⼤法!o(∩_∩)o 这句话可是编程的真谛啊!(如果你看不懂这个梗那你有可能是伪程序员)
其实,话说回来,实在不⽅便⽤英⽂的时候,我认为也可以⽤拼⾳命名。这个问题上可以务实⼀点,量⼒⽽⾏。但是,拼⾳和英语混⽤的做法就不太好了。最好别这样!逼格不⾼。
注释
怎么添加代码注释
关于注释,我们需要解决的第⼀个问题是如何添加代码注释。
对于Java、C#之类的语⾔,有专⽤的⽂档注释语法,很好处理。对于C/C++,则按约定的格式说明⼀下类和函数、代码⽚段的作⽤和意图即可,⾄少编译器会进⾏静态检查。在Python中,有更⽜逼的⽂档字符串这样的语⾔级特性⽀持,看注释⽤help()很⽅便。不过对于Lua这样的弱类型解释型语⾔,注释就⽐较难处理了。这⾥以Lua为例给出⼀种注释的解决⽅案。
借⽤Java语⾔⽂档注释的风格。
⽂件注释,或者说类/模块注释。
函数注释
tips: Lua中可以通过metatable机制实现类和继承,这⼀点与Javascript通过原型机制来实现类和继承有点类似。
注释⾥该写些什么
我们⾸先来看个反例。
⾸先这个⽅法名本⾝就取得不好,这个暂且不说,先说注释问题。这⾥的注释犯了⼏个错:
1. ⽅法注释为“查询”,这简直就是废话!⽅法名已经告诉别⼈这是查询⽅法了,还在这个注释⾥写这两个字有什么意义呢?⽽且到底查询些什么这⾥也没说!
2. 参数没有注释。没有描述每⼀个参数的意义以及取值范围等!
3. 什么情况下会抛出PageIndexOutOfBoundsException没有描述清楚。
4. “定义⼀个整型变量”这种垃圾注释就不要写了,这么简单的语句谁看不懂啊!如果要注释,也是写上
这个变量的含义。
这⾥我们先不考虑设计问题(分页索引号最好做成可以⾃⼰调整成合理值),下⾯再来看改善注释之后的代码。
改完之后的注释有没有感觉信息更全很多!虽然说代码本⾝就是最好的注释,但是必要的注释还是得写上去,毕竟调⽤的时候别⼈没法猜测你的索引号到底从0还是从1开始。另外,如果函数内算法⽐较复杂,可以在代码块内注释,也可以在函数注释上直接写清楚这个函数内部的⼤概算法/逻辑。代码写出来就是给别⼈调⽤的,如果没有基本的注释信息,那么每次调⽤你的代码的时候,都得去看⼀下你的函数内具体逻辑才能知道怎么调⽤。这显然是⾮常低效的!
命名与注释这两个基本⽅⾯没做好的话,会影响到整个团队的运作。也就是说,你封装的东西并没有给队友节省什么时间,别⼈⽤到你的代码的时候,⼜需要花上⼀些时间去读你的代码。如果团队⾥每个⼈都这样,那整个团队都会极其低效。我个⼈是⾮常不愿意与这种代码风格恶劣的⼈合作的。
参考规范
关于编码风格的问题,本⽂只说命名和注释这两个⽅⾯。关于缩进、空格、断⾏、空⾏等其他⽅⾯的
问题,可以参考本节给出的参考规范。
不同的企业会有不同的编码规范,所以这⾥没有办法给出⼀个符合所有公司的规范。不过制定⾃⼰团队的规范的时候,可以参考⼀些⼤企业的做法。以下是世界上最⼤的互联⽹公司⾕歌的编码规范,同学们可以参考这个。
Google Java Style Guide
Google C++ Style Guide
Google Python Style Guide
Google HTML/CSS Style Guide
Google JavaScript Style Guide
异常处理
异常与返回值有什么不同
在C语⾔中,我们的函数通常会返回⼀个整型值作为状态码⽤于通知客户端调⽤的结果。⽐如0表⽰成
功,⾮0表⽰失败。并且可以通过不同的数值来表⽰不同原因导致的失败。然⽽在Java、C#、C++⼀类⾯向对象语⾔中,⼀般不会⽤返回值来表⽰状态。返回值⼀般⽤于表⽰返回的业务值,⽽异常⽤于通知客户端程序运⾏状态改变了。
什么时候需要抛出异常
关于这个问题,我想到了⼀句极其精炼的话:当函数⽆法完成其宣称的任务的时候抛出异常!
⽐如上⾯的那个⽇⼦,当listArticle⽅法由于种种原因⽆法查询出⽂章列表的时候,则抛出异常。
抛出异常在这种场景下是⾮常有必要的,因为这样其他⼈调⽤你的代码时可以⾮常放⼼的去调⽤,只要调⽤了你的⽅法,就会返回⽂章列表。如果⽆法返回⽂章列表,则会抛出异常。完全不⽤在调⽤这个函数的时候去怀疑是否执⾏成功了。
再来⼀句⾄理名⾔:
宁愿终⽌程序也不要带错运⾏下去。
也就是说,遇到错误的时候,宁愿抛出异常终⽌程序,也不要带着错运⾏下去。这是在掩⽿盗铃!
异常需要携带什么信息
⾸先,异常的类型本⾝会带有异常种类信息。其次,异常的message属性可以带上更详细⼀些的信息。这⾥需要注意,千万不要像下⾯这么做。
抛出异常了肯定是执⾏失败了呀!带上这种信息有什么⽤,不是带了⼀句废话嘛!
应该是下⾯这样
⽇志
谈到⽇志,⾸先要搞清楚⼀个问题,⽇志是⼲嘛⽤的?
⽤来记录运⾏时的错误信息啊!
是啊。好像⼤家都知道⽇志是⼲什么⽤的,但是为什么写起代码来就会忘记初衷呢!
来看看代码:
这⾥的代码是什么意思呢?程序员们应该都能明⽩的!很显然,这位程序员是想借助这些标记来调试,想知道代码到底执⾏到哪⼀⾏了。但是,这⾥很明显地犯了两个错。
1. 为什么是System.out.println("");⽽不是logger.debug("");?
2. 为什么是1、2⽽不是⼀些更明确的⽂字信息呢?
在这⾥,合理的⽅式是下⾯这样。
我想给正在犯上⾯的错的同学提个醒:
1. 使⽤⽇志框架,并⽤合适的级别输出⽇志⾮常重要。
好多程序员从来不负责也不参与运维相关的⼯作,甚⾄是做了好⼏年的Web都从来没有⾃⼰发布过⽹站。所以压根没有后期维护的意识!
如果没有这些⽇志,当项⽬上线之后,运维的背锅侠兄弟发现⽹站挂了之后只能直接重启,然后当作什么也没看到。因为没有排错的线索。
2. 输出有效信息。
不要去输出⼀些像1、2、3、成功、失败、hello这样的毫⽆意义的⽇志,要输出logger.debug("邮件发送任务成功⼊队。任务id:" + taskId);这样的有效信息。
也许当时你调试的时候,在你看来这些奇怪的字符串是有意义的,但是在其他⼈看来,这些就是天书。运维的背锅侠会提⼑过来砍你的!另外像"-------开始执⾏--------"这种对运⾏期间定位问题没有半点好处的⽇志就不要输出了!⾃⼰⽤可以,提交代码前⼀定要删掉。
3. ⽇志中带上上下⽂信息。
孤⽴的⼀句错误⽇志通常没有什么实际作⽤。⽐如上⾯的例⼦中,如果在不到指定的模板⽂件的时候未将发送邮件时指定的模板⽂件名输出,那么排错的时候⽆法知道到底是少了哪个模板⽂件。
4. 不要在⽇志中输出⽤户的敏感信息。
千万不要在⽇志中输出像⽤户密码、邮件内容之类的涉及⽤户隐私的敏感信息,也不要去输出像验证码的值之类的敏感信息。
参数校验
在你对外公开的⽅法前先插⼊⼀些检查参数的代码,以确保⽅法被“正确的姿势”调⽤。⽐如:
参数校验的作⽤
如果在对外公开的重要⽅法开始的位置不插⼊校验参数的代码,有时恐怕⽅法需要运⾏到⽅法内部⽐较深的位置才会抛出⼀个异常来。⽽且那种情况下,抛出的异常可能就会有各种各样的了。⽐如空指针、除零异常等。
这种情况下,很难⼀眼看出引发这个异常的根源是参数传错了。需要对你的代码进⾏⼀番调试才⾏!如果⼀开始就在代码的⼊⼝插⼊了校验参数的代码,那么调⽤的时候,⼀眼就能看出来是参数传错了导致了⼀个异常。这样其他程序员看到这个异常之后就会去看⼀下你的⽅法注释。他⼀看,哦!原来分页索引号是从1开始计数的,那么这个问题就会就此打住,给团队节省了时间。
参数校验问题是会影响团队运⾏效率的⼀个很关键的因素。所以,请同学们重视起这个问题来。我们都是⼯程师,团队作战的,⾃⼰写代码快不叫快,整个团队快起来才叫真的快!⽤好断⾔,可以让你的代码更健壮。
tips: Java中默认断⾔是不开启的,所以建议⽆视Java语⾔的断⾔,⾃⼰处理。
什么时候需要进⾏参数校验
我认为⼀个⽅法或者函数在满⾜以下条件时有必要进⾏参数校验:
1. ⽅法或者函数是对外公开的,不是私有的。
2. 参数有可能为空指针的时候。
3. 参数的合理值⽆法通过⽅法名、参数名、参数类型⼀眼看出来的时候!⽐如上⾯那个pageIndex是从1开始计数的,但别⼈并不知道你
是从1开始计数的。
如果对每⼀⽅法都进⾏校验的话,其实挺⿇烦的。程序员的时间是很宝贵的,没这么多闲⼯夫。不过在满⾜上⾯条件的情况下,最好还是校验⼀下。因为做了这个校验,你⾃⼰是会稍微浪费⼏分钟的时间,不过从团队整体来看,总的调试损耗的时间却降下来了。要记住⽅法/函数写出来就是给别⼈调⽤的!
参数校验需要做到什么程度
我有⼀个标准,就是把⾃⼰当成调⽤这些代码的那个⼈,把⾃⼰想象成有可能以任何“姿势”调⽤的菜鸟(实际上也有可能是不了解你的代码的⼤⽜)。如果这个时候⾃⼰也有可能会犯某些错(⽐如没注意边界值,没注意是否可空),那么这个时候是必须要做校验的。对于⼀些已经在其他层做过处理不太可能有错误的值的情况,可以不做校验。⽐如你的UserService中有⼀个签名为public void register(User user)的⽅法,⽤于注册⼀个⽤户。这种情况下,可以只校验⼀下user参数是否为空,⽽不⽤对user的username、password属性进⾏校验(⽤户名密码长度是否合法等)。因为你在上⼀层控制器层模型绑定的时候已经做过⾮常严谨的校验了。当然,这⾥如果你有充⾜的时间,也可以校验⼀下。具体做到什么程度,还需要你根据情况去⾃⼰把握。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论