主要参考 代码整洁之道(Robert C. Martin力作,韩磊献译)
前言:假设你是位医生,病人请求你再给他做手术前别洗手,因为那会花太多时间,你会照办吗?本该是病人说了算;但医生却绝对应该拒绝遵从。为什么?因为医生比病人更了解疾病和感染的风险。医生如果按病人说的办,就是一种不专业的态度(更别说是犯罪了)。同理,程序员遵从不了解维护风险的经理的意愿,也是不专业的做法。
所以,单纯实现功能还不够。必须时时保持代码整洁
这事有人会问,什么样子的代码是整洁的呢?
- 代码逻辑应该直接了当,叫缺陷难以隐藏
- 尽量减少依赖关系,使之便于维护
- 依据某种分层战略完善错误处理代码
- 性能调至最优,省的引诱别人做没规矩的优化,弄出没必要的麻烦
- 整洁的代码只做好一件事
- 能通过所有测试
- 体现系统中的全部设计理念
- 没有重复代码,包括尽量少的实体,比如类、方法、函数等
关于命名规范
Unity 之命名规范(一)
Unity 之命名规范(二)
Unity 之命名规范(三)
如果每个程序都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在,那就可以称之为漂亮的代码。
- 名副其实、避免误导
例如 BTName,可以理解为button名称?BigTag?还是变态名字? 或者GetComponentsInChildren与GetComponentInChildren,又或者Namel与Name1
- 做有意义的区分
ProductData与ProductInfo就足以让人迷惑,所以用更准确的名称让人一眼就能看出他们的区别
- 使用不拗口且常用的单词名称
在协作和阅读时降低学习成本
- 使用方便搜索的名称
大量的使用同种相同的单词命名不同含义的对象,在全局搜索时分分钟出现99+记录
- 使用与官方一致的命名格式
笔者每次看到混合使用匈牙利、帕斯卡、驼峰命名方式的编码,都感觉是在阅读一种不熟悉的语言。
- 避免思维映射
例如ProductList的命名对象实际是一个数组而不是列表
- 类的名称应该是名词或名词短语,不应当是动词
正确的如Customer、WikiPage、Account、AddressParser,避免使用的如Manager、Processor、Data、Info,且类名不应该是动词。
- 方法名应该是动词或动词短语
如deletePage或save
- 每个概念对应一个词
controller、manager其实表示一个对象,这种命名应该避免,函数名称应当独一无二,而且要保持一致。
- 别用双关语
避免同一个单词用于不同的目的,如两个AddOnClick但含义不同的函数,反之,同种含义的变量在不同类中名称应该保持一致。此时让我想到了【我喜欢上一个人】这句话的含义 XD~~~
- 使用解决方案领域名称
例如一个医疗先关的系统你里面的单词命名使用计算机相关的术语要好于医疗相关的术语
- 使用源自所涉问题领域的名称
如果找不到好的命名,就用涉及领域的名称吧
- 添加有意义的语境
AddressDescription比Description好
- 取一个好名字,不要总认为好名字都让狗起了
名称的作用范围越大,名称就该越长、越准确。
名称应该说明函数、变量或类的一切信息。不要用名称掩盖副作用。GetObj()
函数的作用是获取一个物体,没有则创建一个。如果换为FindOrCreatObj
则更准确。
2-3月之后再看自己的当时取得好名字,第一印象是这是哪个大佬写的代码?真NB!
- 函数体量要小
20行封顶最佳
- 专一,只做一件事
目的性强,做好这件事,只做一件事
- 每个函数一个抽象层,且符合自顶向下规则
可以理解为只操作一个类中的函数,避免多个类交替调用,阅读方式始终保持从上至下的阅读顺序
- 使用描述性的名称且名称应该表达其行为
InitializedUIAndCheckComponent
要比Initialized
好,看名字就知道这是初始化UI和检查组件。Date newDate = date.addDay(5)
要比Date newDate = date.add(5)
好
- 函数参数尽量少,out ref 缺省参数少用
函数的参数应该少。没有参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。
- 标识参数
少用传入bool这种标识参数,尤其是那种根据bool分别调用不同的函数的情况,bool参数大声宣告函数做了不止一件事。他们令人迷惑,应该消灭掉
- 动词与关键字
给函数取一个好名字,能较好地解释函数的意图,以及参数的顺序和意图。例如函数
writeFiled(name) ;
- 无副作用
保持一个函数只做一件事,不要把一些额外的功能隐藏起来
- 分隔指令与询问
避免返回bool值的操作,如果需要就明确返回bool代表的意图,也就是让调用者知道
return bool
到底什么意思
- 抽离Try/Catch代码块
如果代码块内行数较多,用单个函数代替
- 错误处理就是一件事
处理错误的函数不该做其他事。而且在catch/finaly代码块后面也不该有其他内容
- 别重复自己
重复已经在那里了,尽可能找到并消除它,重复可能是软件中一切邪恶的根源!!!
- 结构化编程
每个代码块应该有一个入口、一个出口。偶尔出现return、break或continue语句没有坏处。另一方面,goto只在大函数中才有道理,所以应该尽量避免使用。
- 注释不能美化糟糕的代码
与其花时间编写解释你搞出来的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。
- 用代码来阐述
有时只需要创建一个描述与注释所言同一事物的函数即可。例如
CheckPlayerPassWord(string passWord)
- 法律信息
例如,版权及著作权声明就是必须和有理由在每个源文件开头注释出放置的内容。
- 对意图的解释
有时,注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。
- 阐释
有时,注释把某些晦涩难明的参数或者返回值的意义翻译为某种可读形式,也会是有用的。
- 警告
有时,用于警告其他程序员会出现某种后果的注释也是有用的。例如知识为什么要关闭某个特定的测试用例
- TODO注释
TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。
- 多余的注释
读一些注释花的时间没准比读代码花的时间还长,值得编写的注释,也值得好好写。如果要编写一条注释,就花时间保证写出最好的注释。字斟句酌。使用正确的语法和拼写,别闲扯,别画蛇添足,保持简洁。
- 误导性注释
例如Unity3D中UGUI组件Toggle,其中设置isOn属性会触发回调,但是有一个前提,设置的状态(True或者False)需要与原来的状态不同,相同则不触发,如果你的注释写设置状态触发回调,如果原来是false状态,再次设置false没有触发回调,调用者就很有可能写出BUG。注释应该写状态发生更改才会触发回调。
- 循规式注释
任何字段属性函数都要求有注释有时没有必要,而且冗余注释还会让代码变得散乱。
loginPassWord
的注释是登录密码,这种注释没有任何意义
- 能用函数或者变量是就别用注释
一个涵盖多个功能的函数可以拆分成多个含有自我描述说明(函数的名称具有说明性)的小函数。
- 废弃的注释
过时。无关或者不正确的注释就是废弃的注释。
- 注释掉的代码
看到被注释掉的代码会令我抓狂。谁知道他有多旧?谁知道它有没有意义?没人会删除他,因为大家都假设别人需要它或是有进一步计划。看到注释掉的代码就删除它!难道留着他过年?!!!
- 信息过多
控制你注释的数量,删除不必要的代码。
- 向报纸学习
标题名称一目了然,保持从上向下阅读方式
- 垂直方向上的靠近
合理使用用空白行标识代码中代码的紧密关系。
public void OpenPanel (Animator anim)
{
if (m_Open == anim)
return;
anim.gameObject.SetActive(true);
var newPreviouslySelected = EventSystem.current.currentSelectedGameObject;
anim.transform.SetAsLastSibling();
CloseCurrent();
m_PreviouslySelected = newPreviouslySelected;
m_Open = anim;
m_Open.SetBool(m_OpenParameterId, true);
GameObject go = FindFirstEnabledSelectable(anim.gameObject);
SetSelected(go);
}
static GameObject FindFirstEnabledSelectable (GameObject gameObject)
{
GameObject go = null;
var selectables = gameObject.GetComponentsInChildren (true);
foreach (var selectable in selectables) {
if (selectable.IsActive () && selectable.IsInteractable ()) {
go = selectable.gameObject;
break;
}
}
return go;
}
- 横向格式
控制一行的字符,上限120个字符
- 团队格式
有一个团队统一的规范
- 数据抽象
隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!类并不简单地用取值器和赋值器将其变量推向外间,而是曝露其接口,以便用户无需了解数据的实现就能操作数据本体。
47.得墨忒耳律
避免链式调用
//灾难
Debug.Log(cSharpSix?.FirstName2==null ? cSharpSix.number_Null?.ToString() : "三元运算菜鸟海澜");
Debug.Log(cSharpSix?.number_Null?.ToString()??"defualt菜鸟海澜");
- 数据传输对象
传输的数据结构里面应该只有纯数据和对应的赋值、取值器,避免其他逻辑函数混入
- 使用异常而非返回码
遇到错误时,最好跑出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
- 依调用者需要定义异常类
使用异常类来区分不同错误。如果你想要补获某个异常,并且放过其他异常,就使用不同的异常类。
- 整洁的边界
引用第三方库使用适配器保持接口一致,换句话就是使用一致的API
- 每个测试一个概念
示例及测试保持每个测试函数中只测试一个概念。我们不想要超长的测试函数,测试完这个又测试那个。
快速、独立、可重复、自足验证、及时
- 恰当的信息
通常,作者、最后修改时间、SPR数等元数据不应该在注释中出现。注释只应该描述有关代码和设计的技术性信息(都写了要版本控制器何用???)
- 需要多步才能实现的构建
构建系统应该是单步的小操作。俗话就是简单几步就能运行
- 需要多步才能做到的测试
你应当能够发出单个指令就可以运行全部单元测试
- 死函数
永不被调用的方法应该丢弃。保留死代码纯属浪费
- 一个源文件中存在多种语言
理想的源文件包括且只包括一种语言。现实中,我们可能会不得不使用多余一种语言,但应该尽力减少源文件中额外语言的数量和范围
- 明显的行为未被实现
如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉。他们不再信任原作者,不得不阅读代码细节。通俗的讲就是调用者根据函数名称或者注释理解这个函数应该有的功能,但是并没有实现。
- 不正确的边界行为
每种边界条件。每种极端情形,每个异常都能代表了某种可能搞乱优雅而直白的算法的东西,别依赖直觉追索每种边界条件,并编写测试。(各种极限测试)
- 忽视安全
关闭某些编程器警告(或者全部警告!)可能有助于构建成功,但也存在陷于无穷无尽的调试的风险。关闭失败的测试,告诉自己过后再处理,这和假装刷信用卡不用还钱一样坏。
- 信息过多
类中的方法越少越好。函数知道的变量越少越好。类拥有的实体变量越少越好
- 死代码
就是不会执行的代码。可以在检查不会发生的条件的if等语句体中找到 例如:
if(true) return;
但是后面还有一堆代码
- 使用解释性变量
让程序可读的最有力方法之一就是将计算过程打散,用有意义的命名单词变量转换成中间变量,然后进行拼凑。
public bool InitializedTest()
{
bool isSuccessfulStep1 = InitializedStepOne();
bool isSuccessfulStep2 = InitializedStepTwo();
return isSuccessfulStep1 && isSuccessfulStep2;
}
- 用命名常量代替魔术数
术语“魔术数”不仅是说数字。它泛指任何不能自我描述的符号。例如:
string 牛逼 = "666";
- 避免否定性条件
if(IsComplete())
好于if(NotComplete())
兄弟,如果你遇到一个只要求速度,只满足眼前需求的经理,恰巧你又缺钱,GameObject.Find这种函数,Test123这种命名可以考虑用起来了,真的,别跟钱过不去。。。。