给函数和变量取个好名字是优秀程序员的基本功,取名的基本要求是 名副其实,见文知意。如果名称需要注释来补充,那就不算是个好名字。
var d // 日期
修改为:
var date
取名的第二个要求是避免误导。比如数据本身不是一个 list,那就别用 list 来命名,因为 list 一词对程序员有特殊的含义。
var personList = mapOf("Martin" to "Author", "LeetCode" to "Reader")
修改为:
var personMap = mapOf("Martin" to "Author", "LeetCode" to "Reader")
取名的第三个要求是去掉冗余。Variable一词永远不应当出现在变量名中,Table一词永远不应当出现在表名中。nameString 会比 name 好吗?ProductInfo 、 ProductData 和 Product 有什么区别?更糟糕的是,如果代码中同时存在 Article 和 ArticleInfo 类,程序员怎么知道该调用哪个类呢?多个意义含混的冗余词汇只会让阅读者困惑,要区分名称,就要以读者能鉴别不同之处的方式来区分。
例如:开始你用了 address 变量表示用户居住地,如上海、北京。后来又要求更详细地描述用户的居住地。如上海黄浦区、浦东区,北京海淀区、朝阳区。用什么来命名这个详细地址呢?detailAdress?smallAddress?还是 anotherAddress?这些统统都不是好的做法,牢记上述法则:以读者能鉴别不同之处的方式来区分,这时比较好的做法是修改之前的 address 变量名字为 city,再将区域的地址命名为 district。
取名的第四个要求是:严谨,不要俏皮。笔者曾经接手一位外国同事写的代码,在一个类中,这位外国同事使用了 wearClothes、wearPants 命名函数,之后又出现一个 startParty 函数。仔细理解后,笔者才发现,这是代表软件系统的第一步准备、第二步准备,然后正式启动这三个流程。或许这位同事写这个类时心情不错,将其比喻成了一个 party 的流程,但对于读者来说,梳理这三个函数的意思着实要费一番心思。事实上,此时我们最好将其命名为 firstStepOfPreparation、secondStepOfPreparation、systemBoot,宁可明确,毋为好玩。
函数和类应该坚持 单一权责原则。保持高内聚,低耦合。隔离会让系统每个元素的理解变得容易。
单一权责原则:在面向对象编程领域中,单一权责原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。一个类或者模块应该有且只有一个改变的原因。
过长的函数会造成不易理解,如果某天这个函数需要修改的话,一个长长的函数会大大增加理解成本。并且,小函数也能更好地复用。
如果一个函数做了多件事,一个明显的标志是无法为它起一个精准的名字。你会觉得需要函数名需要使用 and 连接,比如 calculateAndPrintPrice,这时候最佳做法是将其拆分为 calculatePrice 和 printPrice 两个小函数。
函数的第二个规范是 尽量不要在参数中传递状态值,状态值是函数做了多件事的明显标志。例如:
fun setLoading(status: Boolean) {
if (status) {
loading.VISIBILITY = View.VISIBLE
} else {
loading.VISIBILITY = View.GONE
}
}
修改为:
fun showLoading() {
loading.VISIBILITY = View.VISIBLE
}
fun hideLoading() {
loading.VISIBILITY = View.GONE
}
函数的第三个规范是 同一个函数中的代码应该属于同一层级。良好的软件设计要求分离位于不同层级的概念,较低层级概念和较高层级概念不应混杂在一起。
fun print() {
printTitle()
// 打印详情
System.out.println("details:")
System.out.println("It's a simple sample")
}
修改为:
fun print() {
printTitle()
printDetails()
}
fun printDetails() {
System.out.println("details:")
System.out.println("It's a simple sample")
}
注释并不是越多越好,有的注释纯属无意义的废话,例如:
// 如果count > 0,返回1,否则返回-1
if (count > 0) {
return 1
} else {
return -1
}
这些注释看起来就像是喃喃自语,或许读者阅读这些注释的时间比读代码还要长。
好的注释只应该用在必要时,用于警告其他程序员会出现某种后果的注释是有用的,例如:
fun getDataFormat(): SimpleDateFormat {
// SimpleDateFormat不是线程安全的,所以我们需要每次创建新的实例
val df = SimpleDateFormat("yyyy-MM-dd")
df.timeZone = TimeZone.getTimeZone("GMT")
return df
}
但,最好的注释是没有注释,若代码足够有表达力,用代码来展示意图往往会更好。注释总是一种失败。当我们无法找到不用注释就能表达自我的方法时,我们写了注释,这并不值得庆贺。因为注释常常会撒谎。原因很简单:程序员不能坚持维护注释,尤其是别人写的注释。当另一个人修改了代码后,往往不会去阅读上一个人写的注释,再修改注释。所以注释常常会与其所描述的代码分割开来,孑然飘零,越来越不准确。
写注释的常见动机之一是原有代码混乱。当我们阅读代码时,发现已有的代码令人困扰、乱七八糟。这时我们也许会告诉自己:“等我阅读清楚后,给它写点注释!”别那样做!最好的做法是把代码整理干净。
// 这个初始化函数用来初始化数据和初始化视图
fun init(){}
修改为:
fun initData(){}
fun initView(){}
在我们阅读报纸时,在顶部,你期望有个头条,告诉你故事的主题。然后第一段是整个故事的大纲,给出粗线条概述,但隐藏了故事细节。接着读下去,细节渐次增加,直至你了解所有的细节。
代码的格式也要像新闻文章一样,最顶部给出高层次概念,向下渐次展开细节。函数应该紧跟调用处,保证垂直方向上的靠近。如果格式混乱,读者在阅读时总会滑上滑下,导致思维跳跃,增加不必要的理解难度。
影响格式的第二个要素是 缩进与间隔,现代化的 IDE 都有格式化代码快捷键,你也可以在设置中搜索"Reformat Code",自定义格式化代码快捷键。随时格式化,并去掉多余的空行,让我们的代码保持清爽、整洁。
在有的源代码中,作者采用长长的链式调用,甚至会鼓吹自己只写了一行代码便实现了此功能。事实上,绝不应该为了节省变量,写过长的链式调用,否则容易造成"火车失事"。正确的做法是遵守“得墨忒定律”:适当拆解链式调用,只和朋友谈话,不和朋友的朋友谈话,使得代码阅读和调试都更方便。
得墨忒定律(The Law of Demeter):模块不应该了解它所操作对象的内部情形。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。