前言
由于本人是非计算机科班,对于代码缺乏一些基础素养,就导致在刚刚入职的半年时间,写了一堆烂代码。平时在学校,写代码能运行可以,根本没有考虑过,性能、内存、健壮性,特别是健壮性,由于工作后没有注意到这个问题,线上的代码缺少健壮性,因为这个造成的上亿用户体量项目的线上事故,损失名声,甚至丢失饭碗的事情是很多的。痛定思痛之后决定要提升基础素养,前一段时间看完了Bob的代码整洁之道,今天抽空决定输出一篇文章。要不就真的是走码观花了,哈哈哈哈哈哈。
这本书目前是分了3部分,在译本里没有说明白具体到哪一章,我总结了一些我以前没有意识到的代码规范。 这里说到的代码整洁 我理解是分为 代码规范
+ 代码架构
两部分,所以我后面说到的可能也是在整洁中包含了架构设计的相关思考。
一 整洁代码的原则
1.混乱的代价
关于大佬们对于代码整洁的看法以及混乱代码产生的问题,后者我已经用的职场教训来验证了,深刻! 前者我觉得对于代码整洁性 一个是对于代码的热爱,另一个就是项目复杂度提升过程中遇到问题-->解决问题 -->积累经验的一个过程,如果一直都是写一些UI项目,可能我对于项目整洁以及架构的设计就没有深入思考以及设计。
为了项目的长时间便于维护,让项目比你来时更干净。
2.有意义的命名
2.1 变量、函数、参数、类、封包命名
长名称胜过短名称,搜得到的名称胜过自己编造的名称
- 变量名
m开头
- 函数名字
动词+名词
- 类
名词/名词短语
2.2 名副其实、避免误导、做有意义的区分、使用读的出来的名称、使用可以搜索的名称、无需使用成员前缀
3.怎么样让函数看起来整洁?
- 保持短小(20行封顶最佳)
- 保持缩进层级不超过2层
- 只做一件事(做哪一件事呢?)
- 简单理解就是不要糅杂代码
java
//错误代码(多层嵌套,并且不知道代码逻辑)
public void testableHtml(){
if
.......
if(){
......
}else{
......
}
}esle{
.....
}
}
//错误代码(多层嵌套,并且不知道代码逻辑)
public void testableHtml(){
if(){
//判断是否为测试html
return;
}
//else相关部分
}
-
函数保持一个抽象层级
js//错误示范 public void testableHtml(){ //判断是否为测试网页 string str = ""+value; int num = Integer.parseInt(str); //执行相关测试网页方法 //返回结果 } public void testableHtml(){ //判断是否为测试网页 //数据处理 //执行相关测试网页方法 //返回结果 }
-
函数参数越少越好(如果参数2个以上,那就应该考虑将参数封装成一个对象)
js
public static void main(){
person1 = new Person("aa","name");
person2 = new Person("bb","name);
swap(person1,person2);
//swap之后,person1依然指向"aa",person2依然指向"bb"
sout(person1.getName());
sout(person2.getName());
}
public static void change(Person person1, Person person2){
person temp = person1;
person1 = person2;
person2 = temp;
}
- try/catch 代码尽量抽离来保持结构整洁(try/catch本身就是专注做一件事)
4.注释
注释在整篇中贬义大于褒义,好的代码是不需要过多的注释的,但是由于历史需求的原因有一些注释还是方便后续程序员快速接手项目。
- 写注释的目的 避免为自己的糟糕函数、类的名称来做解释,如果你的目的是这样,那你的名字一定是没有写好的,读到这里发现自己在工作中确实很多这种做法,哈哈哈哈,真低级。
- 还有一个注释的重要作用就是告诉后来人,不要乱改动某些地方
- 或者一些注释是把项目的需求文档作为注释
5. 格式
什么是格式呢? 函数的最大行数,前面讲过 一个函数最长20行左右是最好的,代码在垂直方向上有间隔,没有必要一行挨着一行,第一眼看上去就失去了代码的美感。最起码每个函数之间你得空一行吧。下面的代码就是很好的一个例子
1. 垂直方向
- 静态常量名称应该全部大写,单词用下划线分割;
- 变量尽可能放在开头,方便找到
js
/**
* Helper class that is used to provide references to initialized RequestQueue(s) and ImageLoader(s)
*/
public class MyVolley {
private static RequestQueue mRequestQueue;
private MyVolley() {
// no instances
}
public static void init(Context context) {
mRequestQueue = Volley.newRequestQueue(context);
}
static RequestQueue getRequestQueue() {
if (mRequestQueue != null) {
return mRequestQueue;
} else {
throw new IllegalStateException("RequestQueue not initialized");
}
}
}
js
/**
* Helper class that is used to provide references to initialized RequestQueue(s) and ImageLoader(s)
*/
public class MyVolley {
private static final String LOCAL_NUMBER = "locl_number";
private static RequestQueue mRequestQueue;
private MyVolley() {
// no instances
}
public static void init(Context context) {
mRequestQueue = Volley.newRequestQueue(context);
}
static RequestQueue getRequestQueue() {
if (mRequestQueue != null) {
return mRequestQueue;
} else {
throw new IllegalStateException("RequestQueue not initialized");
}
}
}
2.水平方向
水平方向不要超过120个字符
,相信一些程序员,特别是新手,就是死也要把你的判断条件都累加在你的if里面的判断条件,像下面这样,最好不要被老板看懂你这样写代码,否则,你离被开除不远了。
js
@OnTextChanged({R.id.double_binding_num1, R.id.double_binding_num2})
public void onNumberChanged() {
float num1 = 0;
float num2 = 0;
//Bad code
if (_number1 =!null && TextUtils.isEmpty(_number1.getText()) &&!isEmpty(_number1.getText().toString())) {
num1 = Float.parseFloat(_number1.getText().toString());
}
if (!isEmpty(_number2.getText().toString())) {
num2 = Float.parseFloat(_number2.getText().toString());
}
_resultEmitterSubject.onNext(num1 + num2);
}
赋值操作符周围加上空格字符
,达到强调的目的,同时也很美观,特别注意的是针对不同的优先级加入空格,也使得代码更加易读
js
public double caculateAnt(double a,double b, double c) {
return b*b - 4*a*b*c;
}
6. 对象和数据结构
1.数据抽象(不想暴露数据的具体,就使用接口,实现类去实现接口)
优点:
面向过程主要是在不改动既有的数据结构的前提下添加新的函数
面向对象则是在不改动既有函数的前提下添加新的类
缺点:
面向过程难以添加新的数据结构
面向对象难以添加新的函数,因为要修改所有实现类
- Demeter Law (最少知道原则)模块不应该了解它操作对象的内部情形(感觉书里讲的不够好,自己百度了下) 得墨忒耳定律可以简单地陈述为"只使用一个.算符"因此,a.b.Method()违反了此定律,而a.Method()不违反此定律,尽量降低类与类之间的耦合。 就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。
3.数据对象
最为精炼的数据结构,就是只有一个公共变量,没有函数的类(DTO,Kotlin里的DataClass)。
本章的数据结构和对象 想要传递的思想大于字面意思,感觉不够白话,或者过于直译,没有做知识的扩展,或者是我阅读的顺序有误,在底子不是很好的前提下,就读了这本书。 面相对象暴露行为,隐藏数据,这个就是高内聚,低耦合;这种思想的好处是便于在添加新的对象时无需修改现有的行为。
7. 错误处理
前一段时间爆发了一个线上事故,这是我刚刚入职时写的,本来以为是我代码有问题,没有数据校验以及兜底,后来在代码review过程中发现。虽然使用了try/catch,但是由于不了解原理,哈哈哈哈哈哈,我在catch里面又throw 了一个新的Exception,这就导致应用程序一直崩溃。 我就知道,需要在trycatch上下点功夫了,这里也是欠下一篇文章。
php
//错误示例(线上项目不要这么干)
try {
......
}catch (Exception e){
throw new RuntimeException(e);
}
8. 边界
9. 单元测试
针对单元测试,我这边目前不是主要学习范围,大体看了一下,一个好的程序员是要学会边写代码,边写测试代码,但是目前我们都是没有这样做。但是质量测试对于整个项目的健壮性还是有很大的作用的。
10. 类
-
物以类聚,
类应该短小
-
类的单一权责原则,类或者模块应用只有一条修改的理由。什么意思呢?(短小且高内聚)
- 单一职责的划分界限并不是如马路上的行车道那么清晰,很多时候都是需要靠个人经验来界定。当然最大的问题就是对职责的定义,什么是类的职责,以及怎么划分类的职责。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
示例
js/** * An HTTP stack abstraction. */ public interface HttpStack { /** * 执行Http请求,并且返回一个HttpResponse */ public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
HttpStack中这个函数的职责就是执行网络请求并且返回一个Response。它的职责很单一,这样在需要修改执行网络请求的相关代码时我们只需要修改实现HttpStack接口的类,而不会影响其它的类的代码。如果某个类的职责包含有执行网络请求、解析网络请求、进行gzip压缩、封装请求参数等等,那么在你修改某处代码时你就必须谨慎,以免修改的代码影响了其它的功能。但是当职责单一的时候,你修改的代码能够基本上不影响其它的功能。这就在一定程度上保证了代码的可维护性。注意,单一职责原则并不是说一个类只有一个函数,而是说这个类中的函数所做的工作必须要是高度相关的,也就是高内聚。
11. 系统
1.工厂模式。 2.依赖注入。 想要搞清楚依赖注入 与工厂模式 的区别,则必须先要了解控制反转(IoC)
控制反转(Inversion of Control,IoC)
控制反转(Inversion of Control) 是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。其基本思想是:借助于"第三方"实现将原本有依赖关系的对象之间解耦。依赖注入(DI) 与工厂模式(factory) 都是其实现的手段
12. 重构迭代
1.设计规则
使用 依赖注入、接口、抽象等工具减少耦合
2.设计规则 重构
- 切分关注面
- 缩小函数和类的尺寸
- 选用更好的名称
- 代码抽取 增加复用 减少重复工作
13. 并发编程
对象是过程的抽象,线程是调度的抽象
并发编程是老生常谈了,需要我们多多学习,日常开发中考虑到数据的原子性。 对于并发编程,与之关联性较强的几种情况
对应上述问题的主要解法,就是考虑到设计模式,观察者者模式、构造者模式
二 复杂案例研究
14. 逐步改进与优化
三 启示与灵感