老码农:如何写出让自己满意的代码

今天有位朋友在微博上问我这样一个问题:

“@老码农的自留地 ,最近出于学习目的写一个管理系统,越到后边,越觉得自己前边的代码写得烂。前辈,我想让代码写得更好一点,能不能谈谈你的经验,给我指点一下!”

我在回复里刚写了几句,就意识到140个字很难把我的想法说清楚,本着知无不言言无不尽的好为人师精神,我决定把我的回答写成一篇博文。

首先要说明的是,我写这篇博客并不代表本人觉得自己的代码写得有多好。事实上我很清楚自己的水平,作为一个做应用系统的程序员,和那些做框架做系统的大牛根本就不在一个层次。而且即使在应用层次,我的水平大概也只能算二三流,只是因为热爱编程所以一直在努力而已,但不管怎么说,能做自己喜欢的工作我已经很满足了。所以我稍微篡改了一下问题,针对“觉得自己前边的代码写得烂”这个重点,把这位朋友问的如何“让代码写得更好一点”改成了“如何写出让自己满意的代码”

言归正传,我自己的体会是写代码很像写作文,开始写之前的构思过程是最关键的。记得高中的时候,有位语文老师给我传授的经验是,至少花三分之一的时间来构思,反复斟酌中心思想、各个段落的大意,文章的脉络,主要的修辞手法,等等。把这些要素都想清楚了,写起来就可以一气呵成。

我觉得写代码也是一样,思路是最关键的。假定采用的技术平台、框架、工具等已经确定了,那么在开始动手写之前,花三分之一以上的开发时间去把所有的数据结构及其相互关系考虑清楚。例如需要定义几个类,类和类之间的关系是怎样的,每个类里都有什么属性,每个类提供一些什么样的方法,等等,这些是最核心的。这些数据结构要考虑得尽可能细,比如功能实现可能没问题,但是性能上不理想,这就说明你的数据结构设计还需要改进。这些细节要反复考虑,交叉检验,直到自己觉得很周到了为止。在此基础上,再注意实现的细节、测试用例、代码可读性,就应该可以写出让自己满意的代码。具体说明如下:

1. 数据结构和核心算法

关于数据结构的重要性,大神Linus Torvalds讲过这样的话,我觉得非常赞同:”Bad programmers worry about the code. Good programmers worry about data structures and their relationships.” (低水平程序员总在考虑代码,高水平程序员总在考虑数据结构及其之间的关系)

数据结构考虑清楚了,核心的算法自然就出来了,这就是关于每个类的每个方法如何实现的问题。比如需要实现一个中位数查询方法,如果你前面确定了数据保存的格式是一个列表,那么你可以考虑采用插入排序法;如果数据格式是自平衡二叉排序树(AVL),则只需直接返回根节点就可以了。

数据结构决定算法,所以你在考虑数据结构的时候,一定要尽可能地使数据的结构和它的自然属性相匹配,不然后面的实现就会是一场噩梦。比如,你把一个多层级的结构定义成二维数组,看上去也靠谱,相当于在一个表格里维护一个组织结构图,可是当你做到部门增减的时候,本层级的数组元素移动自不必说,下面各个层级的元素移动就很容易乱套,而且性能很差,可能你写了2000行代码还有很多边界条件会出错。相反,如果用一个孩子兄弟链表来表示这个树型结构,操作起来就非常容易,可能100行都足够了。

2. 功能实现

思路确定后,实现过程也需要大量的构思活动。碰到你比较熟悉有经验的领域,你自然可以轻车熟路,但难免会有一些你不太熟悉的技术需要尝试。有的同学比较排斥这种领域,比如我好不容易才掌握了Struts 2,领导又让我去学习Grails框架,我就会觉得很不爽,大概看了看就挑出它的一堆问题,然后能躲多远就躲多远。可是我要说,这样的心态会阻碍自己不断提高技术水平。作为一个程序员,最大的挑战也是最大的乐趣所在,就是不断学习新的技术,没有这样的心态,很快就会落后。

好,那么遇到不熟悉的技术怎么办?我的体会是,先不要急着实现项目中的代码,自己另外维护一个测试项目,在里边边查文档边学习,边做一个小功能,把所有需要在项目中实现的功能先在测试项目里跑通,然后再写项目里的代码。这样做的好处是把单个技术问题和其他潜在的bug隔离开来,便于快速学习新技术。否则,你直接在项目里写代码出错以后,要判断问题的源头都要多费好几倍的精力。

3. 测试

测试很重要,设计测试用例就像开发时设计数据结构一样,也是很关键的。在设计测试用例的时候,要把当时自己设计数据结构的思路全部忘掉,或者找别人来设计测试用例,不然会不由自主地测试那些你已经考虑到了的地方。这样测试是跑通了,用户一用起来可能各种边界条件会到处出问题。

有人会推崇TDD的方法,先设计好测试用例,然后在开发过程中确保所有测试通过。我个人不喜欢这种方法,虽然承认从开发质量管理和长期维护的角度来说TDD是很有必要的,但我个人尝试的结果是,设计完测试用例后,想到开发的目标不是实现功能,而是为了跑通测试,就感到毫无乐趣可言。这一点我自己也觉得很矛盾。

写到这里我又想到大神Linus说过的另一句话:”Regression testing” What’s that If it compiles, it is good; if it boots up, it is perfect. (“回归测试”?这是什么东西?如果代码能编译就是好的,如果它启动了,那就是完美的。)
当然了,大神水平摆在那里,他有资本目空一切,咱确实没资格仿效。但是我还是觉得TDD也有TDD的问题,测试是很重要,但把它摆到驱动开发的高度,就有点本末倒置了。这个是我自己的一点看法,本人对TDD了解得不深入,如果有谬误之处,请多多指教。

4. 代码可读性

要想自己满意,代码的可读性一定要好。要做到一年后甚至几年后你拿到自己写的代码,还能很容易看明白当时的思路和实现。这就涉及到命名和注释的问题。

命名就像超市里的商品标签一样,要让看得人一目了然就知道这是个什么东西,比如你的员工类里有两个属性分别是到岗日期和离职日期,把它们定义成date1和date2就没有多少可读性,而定义成dateOnBoard和dateQuit就比较清晰。

注释也是很重要的,它可以用来说明一段代码的作用,算法的设计思想,或者是方法调用的参数格式要求等。有人觉得命名就是注释,代码本身就为自己代言了。我觉得这种说法用来强调命名规范的重要性是很好的,但是因此说不需要注释则有失偏颇。试想,如果Dijkstra首次发明最短路径算法的时候,他给出的代码里没有一行注释,即使所有的变量命名都定义得准确而严谨,又有几个人能看懂他的算法呢?所以,在重要或者复杂的地方,都需要详细地写一些注释,便于看代码的人清晰地了解你的思路。

最后总结一下:要想写出自己满意的代码,首先不要急于动手,要先仔细想清楚思路性的东西,尤其是数据结构,然后在实现过程中大胆尝试小心验证,设计好测试用例,确保代码的可读性,就可以在代码中表现出自己的最高水平。但毕竟各人水平是有差异的,自己满意并不等于其他人欣赏。我对此的看法是,不求尽如人意,但求无愧我心,足矣。最后再啰嗦一句,技术水平是可以慢慢提高的,但是好的编程习惯需要从一开始就养成,它会让你在前进的道路上事半功倍,受益终生。

【更新】针对评论中提到的需求不确定导致设计和实现困难的问题,我又写了一篇有关需求分析的文章:《关于需求分析的几点体会》,欢迎批评指正。

本文作者: 伯乐在线 - 老码农
本文链接: http://blog.jobbole.com/47966/

转载于:https://www.cnblogs.com/jjdiaries/p/3365875.html

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

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

相关文章

Python 爬虫进阶三之 Scrapy 框架安装配置

初级的爬虫我们利用 urllib 和 urllib2 库以及正则表达式就可以完成了,不过还有更加强大的工具,爬虫框架 Scrapy,这安装过程也是煞费苦心哪,在此整理如下。 官网 官方安装文档 安装python 安装 Python 安装过程我就不多说啦&a…

泛型类、泛型方法及泛型应用

泛型类、泛型方法及泛型应用 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安…

Exynos4412 中断驱动开发(二)—— 中断处理流程分析

前面已经学习了中断的注册过程,下面由一张流程图来看一下当中断发生时的处理流程: 中断发生之后处理流程 a -- 具体的CPU architecture相关模块进行现场保护,然后调用machine driver执行对应的中断处理handler; b -- machine driver对应中断处…

用For循环加cat按顺序合并文件

工作目录下面有mydoc1.txt,mydoc2.txt...mydoc41.txt,本来想用sed排列依次取值排序,然后用cat来合并这些文件,发现达不到预期效果,合并令如下所示:ls -lF *.txt | sed -n /mydoc1/,/mydoc41/p | xargs -i …

Python 函数装饰器

装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。大多数初学者不知道在哪儿使用它们,所以我将要分享下,…

vim永久取消空格颜色

这是无意中发现的 vim 随便一个文件的时候空格变成某种颜色,感觉太显眼了 而:set nohsl只能一次修改 而且在执行:set nu 下是不可执行 我们只需在编辑中执行 :.,s/hsl/nohsl/gc转载于:https://www.cnblogs.com/spaceport/p/6379435.html

Exynos4412 中断驱动开发(一)—— 中断基础及中断的注册过程

一、中断基础概念 所谓中断,指CPU在执行程序的过程中,出现了某些突发事件即待处理,CPU必须暂停当前的程序。转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。 1、中断分类 a -- 内部中断和外部中断 根据中断的…

调试与分析

一、获取内核转储 大多数Linux发行版默认关闭内核转储功能,可使用 ulimit -c 查看,-c 表示内核转储文件的大小限制,如果为0,表示未开启。 1、可设置为 ulimit -c unlimited 表示无限制,或设置为其它数值,单…

CSS选择器学习笔记

在 CSS 中,选择器是一种模式,用于选择需要添加样式的元素。 “CSS” 列指示该属性是在哪个 CSS 版本中定义的。(CSS1、CSS2 还是 CSS3。) 选择器例子例子描述CSS.class.intro选择 class“intro” 的所有元素。1#id#firstname选择…

rubymine 调试 redmine

1、安装debase和ruby-debug-ide包。(注意版本,rubymine 8.0.2下,bitnami下的版本需安装debase -v 0.2.1版本,网上有文章说用debase -v 0.2.2beta6。容易在rubymine启动调试时出现找不到ruby-debug-ide等错误提示) 启动…

Linux 设备驱动中的 I/O模型(二)—— 异步通知和异步I/O

阻塞和非阻塞访问、poll() 函数提供了较多地解决设备访问的机制,但是如果有了异步通知整套机制就更加完善了。 异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类…

判断链表是否有环

链表有环的情况一般是链表的尾指向前面的节点而不是null,如head->node1->node2->node3->node4->tail->node2,该链表存在环。判断环是否存在可以借助两个指针,一个指针每次迭代只移动一步,第二个指针每次迭代移动…

Python 爬虫进阶五之多线程的用法

我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理。 首先声明一点! 多线程和多进程是不一样的!一个是 thread 库&#xff0…

Tomcat8 连接池

1、所有的tomcat项目共用一个连接池配置 1.1 修改conf->context.xml文件&#xff0c;在Context节点下配置 <Resource name"jdbc/myDataSource" type"javax.sql.DataSource" driverClassName"com.microsoft.sqlserver.jdbc.SQLServerDriver"…

Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O

在前面学习网络编程时&#xff0c;曾经学过I/O模型 Linux 系统应用编程——网络编程&#xff08;I/O模型&#xff09;&#xff0c;下面学习一下I/O模型在设备驱动中的应用。 回顾一下在Unix/Linux下共有五种I/O模型&#xff0c;分别是&#xff1a; a -- 阻塞I/O b -- 非阻塞I/O…

3.改变 HTML 内容

①xdocument.getElementById("demo") //查找元素 ②x.innerHTML"Hello JavaScript"; //改变内容 <!DOCTYPE html><html><body> <h1>我的第一段 JavaScript</h1> <p id"demo">JavaScript 能改变 HTML 元素的…

Python 爬虫进阶六之多进程的用法

python 中的多线程其实并不是真正的多线程&#xff0c;并不能做到充分利用多核 CPU 资源。 如果想要充分利用&#xff0c;在 python 中大部分情况需要使用多进程&#xff0c;那么这个包就叫做 multiprocessing。 借助它&#xff0c;可以轻松完成从单进程到并发执行的转换。mult…

DEFINE_PER_CPU

转自 http://www.unixresources.net/linux/clf/linuxK/archive/00/00/47/91/479165.html 首先&#xff0c;在arch/i386/kernel/vmlinux.lds中有 /*will be free after init*/ .ALIGN(4096); __init_begin.; /*省略*/ .ALIGN(32); __per_cpu_start.; .data.percpu:{*(.data.perc…

HDU 1213 How Many Tables(并查集模板)

http://acm.hdu.edu.cn/showproblem.php?pid1213 题意&#xff1a; 这个问题的一个重要规则是&#xff0c;如果我告诉你A知道B&#xff0c;B知道C&#xff0c;这意味着A&#xff0c;B&#xff0c;C知道对方&#xff0c;所以他们可以留在一个桌子。例如&#xff1a;如果我告诉你…

Linux 设备驱动的并发控制

Linux 设备驱动中必须要解决的一个问题是多个进程对共享的资源的并发访问&#xff0c;并发的访问会导致竞态&#xff0c;即使是经验丰富的驱动工程师也常常设计出包含并发问题bug 的驱动程序。 一、基础概念 1、Linux 并发相关基础概念 a -- 并发&#xff08;concurrency&#…