如何写代码 —— 编程内功心法
写代码就是学一门语言然后开始撸代码吗看完了我的《GoF设计模式》系列文章的同学或者本身已经就是老鸟的同学显然不会这么认为。编程是一项非常严谨的工作虽然我们自嘲为码农但是这工作毕竟不是真正的搬砖我们是软件工程师。编程需要关注的问题太多不仅仅有语言还有算法、数据结构、编程技巧、编码风格、设计、架构、工程化、开发工具、团队协作等方方面面涉及到很多层面的问题。本文将分享一下根据我这几年来的编程经验总结出的一些关于如何写代码的个人见解。由于“跟我混”的一些小伙伴编程功底相对来说比较薄弱所以在此总结一篇“编程内功心法”帮助他们渡过职业生涯的第一个瓶颈期。顺便也造福一下路过的有缘的同学于是有了此文。前言首先思考一个问题何谓编程编程就是写代码吗所谓的编程其实就是不断的对这个现实世界中的问题建立模型并将其固化为代码自动化执行的过程。~ Bug辉 《GoF设计模式 - 解释器模式》在对问题建立模型的过程中我们会遇到非常多不同层面的问题所以我们需要很多领域的知识去解决这些问题。我们需要管理被操作的数据因为数据与数据之前是相互有关联的。将数据结构化通常是编程的第一步。关于结构化数据的相关理论以及实践需要有一个专门的学科分支或者说课题去研究——数据结构。我们需要解决一个具体的问题这个具体的问题如何一步步去解决过程是怎么样子的——算法。我们需要将解决方案进行自动化并以代码的形式进行交付——编程语言。如果将一个抽象的模型进行编码实现如何实现“这个功能”如何实现“那个功能”——编程技巧。问题的规模大了众多代码糅合在一起连程序员自己都看不懂了怎么来拆分、模块化这些代码——设计。代码量已经到了一个人无法完成的地步了需要团队分工合作才能完成了——工程化。你写的代码我看不懂没法调用或者很难调用我写的代码你也看不懂或者很难看懂。还怎么愉快的玩耍——编码风格/编码规范。问题的规模继续扩大到了系统工程的规模了之前学的套路已经不管用了怎么来构建这个系统才能实现正确、安全、高性能、高可用——架构。然而这些也只是一个系统工程中的冰山一角这是一个庞大的体系。也正是因为软件开发需要考虑到的问题太多且团队成员水品参差不齐所以团队开发中并不是每个程序员做的事情都是一样的。每个人都有自己的角色、初级工程师、中级工程师、高级工程师、架构师、CTO。。。所以编程不仅仅只是堆砌代码说到这里我想起来了一件事情——为啥业界普遍鄙视培训出来半道出家的新人人与人的区别是很大的我见过培训出来也很牛的。其实说到底被鄙视的并不是所有人。而是那些培训了几个月之后发现随便找个工作也能拿“高薪”然后还自认为编程很简单的新人。因为这种经历给了他们一种错觉——编程如此简单我培训几个月也会嘛有点像刚学会开车的新司机很嚣张的对老司机说“开车很简单嘛你看我也会啊”。语言和开发工具只是招式这是外功。而编程思想、经验是内功。这些内功并不是靠短短几个月的培训能够掌握的这一点有点像中国制造业和日本制造业的区别。动不动赶英超美可不好。。。编程并不简单这是一件很严肃的事情。不过今天我没有办法介绍完所有的方面或者说到今天为止我也并没能掌握所有领域的知识。所以今天我只是分享一些关于编码本身的一些经验。另外本文主要分享如何写代码并不是如何用Java写代码。所以文章中各种语言都有可能出现。编码风格先来一个圈内的段子。大部分程序员在工作中都很讨厌这四件事情写注释写文档别人不写注释别人不写文档o(∩_∩)o 哈哈。。中枪了没这个段子其实反映出来一个问题即大部分代码都需要通过大量注释和文档来说明才能将意图传达给维护这些代码的程序员然而就像上面的段子说的那样写大量的注释和文档其实是一件很麻烦的事情。所以很多时候由于嫌麻烦注释和文档就没写导致维护代码的人相当的痛苦。这个苦同学们肯定都是体会过的相当于给你个精密仪器要你维护还不给说明书。其实打破上面那个段子描述的那个怪圈的一个很有效的手段就是统一编码风格。优秀的代码可以实现代码即注释代码本身就可以非常清晰的体现出它的意图来让别人可以很容易读懂。这就是所谓的可读性命名计算机科学领域中最难的两件事是命名和缓存失效命名并不简单很复杂。好的名字可以见名知意非常容易理解。之所以说命名难是因为命名的过程同时也是概念提取的过程对问题建立模型需要提取概念并赋予其“术语”。这个过程其实是“万里长征”中最难的一步。毕竟设计也好架构也罢都有成熟的套路可以参考。唯独这个过程是需要程序设计者自己进行充分的思考的创造性工作以下是总结出来的一些命名经验一个类是某物、某事、某人的抽象是数据与行为的集合体。这恰好符合名词的定义因此类名是一个名词方法名或者说函数名是某操作或者某过程的抽象是一个动作。这恰好符合动词的定义因此函数名通常是一个动词。变量名宁可长一些说明清楚用途也不要用a、b、c之类的无意义的名称除非是循环计数器中用i、j、k等约定俗成的一些变量名。比如pageIndex和pageSize就要比取名成i和s好取成这种和用混淆器混淆过后的代码一样的名称没有什么好处如果算法比较复杂的话过一段时间恐怕自己都会看不懂。变量名最好包含变量本身的业务含义。比如ListStudent studentList new ArrayList();就比ListStudent list new ArrayList();好很多。如果同一段代码里再出现一个ListTeacher的话这样就可以很方便的取名为teacherList或者teachers而不是list1和list2这样的毫无意义的名称英文不好怎么办这个问题怎么说呢。。作为一名程序员吧基础的英文还是要懂的。要不然发展也容易遇到天花板学不好编程的。毕竟最新的技术、解决方案、工具都是从国外传过来的。如果是解决一些基础性的问题每天只做做CRUD好像英文确实不怎么用得上。但是一旦遇到一些实质性问题恐怕只能到英文网站上找喽ㄟ(▔ ,▔)ㄏ 不要跟我说你编程可以不需要Stack Overflow。copying and pasting from stackoverflow可是终极编程大法o(∩_∩)o 这句话可是编程的真谛啊(如果你看不懂这个梗那你有可能是伪程序员)其实话说回来实在不方便用英文的时候我认为也可以用拼音命名。这个问题上可以务实一点量力而行。但是拼音和英语混用的做法就不太好了。最好别这样逼格不高。注释怎么添加代码注释关于注释我们需要解决的第一个问题是如何添加代码注释。对于Java、C#之类的语言有专用的文档注释语法很好处理。对于C/C则按约定的格式说明一下类和函数、代码片段的作用和意图即可至少编译器会进行静态检查。在Python中有更牛逼的文档字符串这样的语言级特性支持看注释用help()很方便。不过对于Lua这样的弱类型解释型语言注释就比较难处理了。这里以Lua为例给出一种注释的解决方案。借用Java语言文档注释的风格。文件注释或者说类/模块注释。--[[ Object-oriented helper functions for Lua author: Elvin Zeng date: 2017-8-21 --]]函数注释--[[ create a class with specified super class. if number of parameters is zero, derived class will extends from {}. param superClass super class of target class return derived class --]] local function createClass(superClass) local derivedClass {} -- 省略一堆代码 return derivedClass end--[[ register a new account param user { username username, password password } return registered user --]] local function register(user) endtips: Lua中可以通过metatable机制实现类和继承这一点与Javascript通过原型机制来实现类和继承有点类似。注释里该写些什么我们首先来看个反例。/** * 查询 */ public ListArticle queryPage(int pageSize, int pageIndex) throws PageIndexOutOfBoundsException { // 定义一个整型变量 int offset; // 省略一堆代码 }首先这个方法名本身就取得不好这个暂且不说先说注释问题。这里的注释犯了几个错方法注释为“查询”这简直就是废话方法名已经告诉别人这是查询方法了还在这个注释里写这两个字有什么意义呢而且到底查询些什么这里也没说参数没有注释。没有描述每一个参数的意义以及取值范围等什么情况下会抛出PageIndexOutOfBoundsException没有描述清楚。“定义一个整型变量”这种垃圾注释就不要写了这么简单的语句谁看不懂啊如果要注释也是写上这个变量的含义。这里我们先不考虑设计问题分页索引号最好做成可以自己调整成合理值下面再来看改善注释之后的代码。/** * 列出指定分页的文章 * param pageSize 分页大小。如果等于0则表示查询出所有文章。 * param pageIndex 分页索引号。必须为一个大于0的整数第一页索引为1。 * return 指定分页的文章列表 * throws PageIndexOutOfBoundsException 当分页索引号超出正常范围时抛出即pageIndex小于0或大于最大页索引时。 */ public ListArticle listArticle(int pageSize, int pageIndex) throws PageIndexOutOfBoundsException{ // 第一条文章记录在MySql数据库中的偏移量 int offset; // 省略一堆代码 }改完之后的注释有没有感觉信息更全很多虽然说代码本身就是最好的注释但是必要的注释还是得写上去毕竟调用的时候别人没法猜测你的索引号到底从0还是从1开始。另外如果函数内算法比较复杂可以在代码块内注释也可以在函数注释上直接写清楚这个函数内部的大概算法/逻辑。代码写出来就是给别人调用的如果没有基本的注释信息那么每次调用你的代码的时候都得去看一下你的函数内具体逻辑才能知道怎么调用。这显然是非常低效的命名与注释这两个基本方面没做好的话会影响到整个团队的运作。也就是说你封装的东西并没有给队友节省什么时间别人用到你的代码的时候又需要花上一些时间去读你的代码。如果团队里每个人都这样那整个团队都会极其低效。我个人是非常不愿意与这种代码风格恶劣的人合作的。参考规范关于编码风格的问题本文只说命名和注释这两个方面。关于缩进、空格、断行、空行等其他方面的问题可以参考本节给出的参考规范。不同的企业会有不同的编码规范所以这里没有办法给出一个符合所有公司的规范。不过制定自己团队的规范的时候可以参考一些大企业的做法。以下是世界上最大的互联网公司谷歌的编码规范同学们可以参考这个。