几个珍藏已久的开发技巧,这一波全部分享给你

几个珍藏已久的开发技巧,这一波全部分享给你_第1张图片

一般在代码的过程中我们经常会有一个疑问,怎样的代码才是好代码,怎么评估写的这段代码是否有水平,在项目重构的中需要达到什么目标才可以,在写业务代码时怎么才能摆脱 CRUD 的简单代码。所述都是差一个代码开发规范,设计原则提供基础设计思路,设计模式提供设计模板;设计代码也需要有度,不能过渡设计,会适得其反;复杂的项目怎么做设计,代码开发完成之后又怎么保证代码质量,所以需要一个衡量的标准。

1、SOLID设计原则

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

1.1、SOLOD设计原则简介

几个珍藏已久的开发技巧,这一波全部分享给你_第2张图片

  • SRP: Single Responsibility Principle,一个类或者模块只负责完成一个职责;

  • OCP:Open Closed Principle,软件实体(模块、类、方法等)应该“对扩展开发,对修改关闭”;

  • LSP:Liskov Substitution Principle,子对象能够替换程序中父类对象出现的任何地方,并且保证原来的程序逻辑行为不变及正确性不被破坏;

  • ISP: Interface Segregation Principle,客户端应该不强迫依赖它不需要的接口;

  • DIP:Dependency Inversion Principle,高层模块不要依赖底层模块,高层模块和底层模块之间应该通过抽象来相互依赖,除此之外,抽象不要依赖具体的实现细节,具体实现细节依赖抽象。

1.2、职责单一原则(SRP)

1.2.1、评判标准

标准:一个类或者模块只负责完成一个职责,不要设计大而全的类,要设计的粒度小、功能单一的类,也就是说一个类包含两个或者两个以上业务不相关的功能,就可以说它的职责不够单一。

表现:

  • 类中的代码行数、函数或属性过多,影响代码的可读性和维护性,就需要考虑对类进行拆分;

  • 类依赖其它类过多,或者依赖的类的其它类过多,不符合高内聚、低耦合的设计思想,就需要考虑对类就行拆分;

  • 私有方法过多,考虑能否将私发方法到新的类中,设置为public方法,供更多的类使用,提高代码的复用性;

  • 比较难给定一个适合的名字,很难用一个业务名词概括,说明职责定义的不够清晰;

  • 类中大量的方法都是集中在操作类的几个属性,其它的属性就可以拆分出来。

1.2.2、代码示例

public class User {
    private String id;
    private String userName;
    private String pwd;
    private String phone;
    private String city;
    private String province;
    private String town;
    private BigDecimal balance;
    // get/set省略
}
  • User类对用户基本信息操作比较频繁,但是地址相关信息只在订单相关功能才用到city、province、town可以单独拆分成一个实体;

  • User基本操作基本不用到账号余额,放到这里有些多余;

  • addUser和deleteUser在一定场景下符合职责单一原则,但是业务扩展可能需要对删除做权限控制,就不太符合这个原则,没有一定的准则;

  • queryUserByRoleId虽然查询的是用户相关信息,但引用了其它实体的内容,我们可以认为是职责不单一。

1.3、对扩展开发,对修改关闭原则(OCP)

1.3.1、评判标准

标准:一段代码是否易于扩展,某段代码在应未来需求变化的时候,能够做到“对外开放,对修改关闭”,那就说明这段代码的扩展性比较好。

表现:

  • 添加一个新功能,不可能任何模块、类、方法不做修改;

  • 类需要创建、组装、并且做一些初始化操作,才能构建可运行的程序,修改这部分代码是再所难免的;

  • 我们要做的经量修改的时间操作更集中、更少、更上层、经量让最核心、最复杂的那部分逻辑代码满足开闭原则。

1.3.2、示例代码

public class OcpUser {
    private String id;
    private String userName;
    private String pwd;
    private String phone;
    private BigDecimal balance;
    //get/set省略
}
public abstract class OcpHandler {
    public abstract boolean check(OcpUser ocpUser);
}
public class PhoneOcpHandler extends OcpHandler {
    @Override
    public boolean check(OcpUser ocpUser) {
        if(ocpUser.getPhone().length() != 11){
            return false;
        }
        return true;
    }
}
public class UserNameOcpHandler extends OcpHandler {
    @Override
    public boolean check(OcpUser ocpUser) {
        if (StringUtils.isEmpty(ocpUser.getUserName())) {
            return false;
        }
        return true;
    }
}
public class Ocp extends OcpHandler {
    private List ocpHandlerList = new ArrayList<>();
    public void addAlertHandler(OcpHandler ocpHandler){
        ocpHandlerList.add(ocpHandler);
    }
    @Override
    public boolean check(OcpUser ocpUser) {
        AtomicBoolean result = new AtomicBoolean(false);
        ocpHandlerList.forEach(ocpHandler -> {
            result.set(ocpHandler.check(ocpUser));
        });
        return result.get();
    }
}
public class Test {
    public static void main(String[] args) {
        Ocp ocp = new Ocp();
        ocp.addAlertHandler(new PhoneOcpHandler());
        ocp.addAlertHandler(new UserNameOcpHandler());
        ocp.check(new OcpUser());
    }
}
  • 现在扩展需要改OcpUser新增一个属性,需要校验的属性;

  • 添加一个handler实现check;

  • 在初始化initializeBeans新增一个handler;

  • 在调用的时候加入对应的属性。

1.4、里式替换原则(LSP)

1.4.1、评判标准

标准:里式替换原则和多态类似,但是多态是实现方式,是面向对象的一个特性,是一种编程语言的语法,原则是规范,规范你怎么开发。

表现:

  • 子类违背父类声明要实现的功能,比如说我父类从小到大排序,子类重新这个方法后是从大到小的顺序排序;

  • 子类违背父类对出入、输出、异常约定,入参和出参类型一样,抛的异常类型也必须完全一样;

  • 子类违背父类注释中所罗列的任何特殊说明,实现方法很父类注释方式说明不符。

1.4.2、示例代码

public class Parent {
    public void sort(List list){
        //从小到大逻辑
    }
    public void query(String userName) throws NullPointerException, NotBoundException{
        //查询逻辑
    }

    /**
     * 加1操作
     * @param num 基数
     */
    public void add(int num){
        num++;
    }
}
public class Lsp extends Parent {
    @Override
    public void sort(List list){
        //从小到大逻辑
    }
    @Override
    public void query(String userName) throws NotBoundException {
        //查询逻辑
    }

    /**
     * 减1操作
     * @param num 基数
     */
    @Override
    public void add(int num){
        num--;
    }
}
  • sort排序的逻辑完全很父类相反;

  • 抛出异常类型不一致;

  • 和注解标识做不相关操作;

1.5、接口隔离原则(ISP)

1.5.1、评判标准

标准:接口可以理解成三类,一组API接口集合(业务具体功能点)、单个API接口或者函数(类中的某一个方法)、OOP中的接口(Inteface)概念。

表现:

  • 设计职责更加单一的接口,单一就意味着通用、复用性好;

  • 某些类在实现接口的时候,需要重写一些不需要的方法,做了一些无用功。

1.5.2、示例代码

public interface CommonService {
    public List queryUserName(String id);
    public int deleteUser(String id);
    public List queryRoleName(String userName);
    public int deleteRole(String id);
}
public class RoleImpl implements CommonService {
    @Override
    public List queryUserName(String id) {
        return null;
    }
    @Override
    public int deleteUser(String id) {
        return 0;
    }
    @Override
    public List queryRoleName(String userName) {
        return null;
    }
    @Override
    public int deleteRole(String id) {
        return 0;
    }
}
public class UserImpl implements CommonService {
    @Override
    public List queryUserName(String id) {
        return null;
    }
    @Override
    public int deleteUser(String id) {
        return 0;
    }
    @Override
    public List queryRoleName(String userName) {
        return null;
    }
    @Override
    public int deleteRole(String id) {
        return 0;
    }
}
  • 接口不灵活,用户和角色的接口放在一起要多做一个工作量;

  • 复用性很差;

  • 不利于代码拆分、管理.

1.6、控制反转原则(DIP)

1.6.1、评判标准

标准:依赖反转原则也可以叫依赖倒置原则,高优先级的类不应该强行依赖低优先级的类,而是通过一个抽象类间接的操作,子类也基于抽象类做实现。

表现:

  • 代码是否易于拆分,在代码重构的时候很难对模块之间解耦,就说明依赖关系太死;

  • 低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景;

  • 高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换。

1.6.2、示例代码

public interface IWorker {
    public void work();
}
public class Worker implements IWorker {
    @Override
    public void work() {

    }
}
public class SuperWorker implements IWorker {
    @Override
    public void work() {

    }
}
public class Manager {
    private IWorker worker;
    public void setWorker(IWorker worker){
        this.worker = worker;
    }
    public void manage(){
        this.worker.work();
    }
}
  • Manager具体实现不依赖Worker和SuperWorker类;

  • 通过抽象接口IWorker相互依赖;

  • 具体实现类依赖接口IWorker;

2、其它设计原则

2.1、KISS原则

KISS=Keep It Short and Simple.(尽量保持简单。)

代码是否足够简单是一个挺主观的评判。同样的代码,有的人觉得简单,有的人觉得不够简单。而往往自己编写的代码,自己都会觉得够简单。所以,评判代码是否简单,还有一个很有效的间接方法,那就是 code review。如果在 code review 的时候,同事对你的代码有很多疑问,那就说明你的代码有可能不够“简单”,需要优化啦。

  • 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等;

  • 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高;

  • 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。

2.2、YAGNI原则

KISS=You Ain’t Gonna Need It.(不要做过度设计。)

不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

  • 不要使用同事可能不懂的技术来实现代码;

  • 不要重复造轮子,要善于使用已经有的工具类库;

  • 不要过度优化。

2.3、DRY原则

DRY=Don’t Repeat Yourself.(不要写重复的代码。)

我们在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那我们就不需要考虑代码的复用性。在之后开发新的功能的时候,发现可以复用之前写的这段代码,那我们就重构这段代码,让其变得更加可复用。

  • 减少代码耦合,高度耦合的代码会影响到代码的复用性,我们要尽量减少代码耦合;

  • 满足单一职责原则,越细粒度的代码,代码的通用性会越好,越容易被复用;

  • 模块化更加容易复用,可以直接拿来搭建更加复杂的系统;

  • 业务与非业务逻辑分离,业务和非业务逻辑代码分离,抽取成一些通用的框架、类库、组件;

  • 通用代码下沉只允许上层代码调用下层代码及同层代码之间的调用,杜绝下层代码调用上层代码;

  • 继承、多态、抽象、封装,越抽象、越不依赖具体的实现,越容易复用;

  • 应用模板等设计模式,一些设计模式,也能提高代码的复用性。

2.4、LOD原则

DRY=Law of Demeter(最小知识原则。)

“高内聚、松耦合”是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。不过,这两者并非完全独立不相干。高内聚有助于松耦合,松耦合又需要高内聚的支持。

  • 不该有直接依赖关系的类之间,不要有依赖;

  • 有依赖关系的类之间,尽量只依赖必要的接口;

  • 迪米特法则是希望减少类之间的耦合,让类越独立越好;

  • 每个类都应该少了解系统的其他部分;

  • 一旦发生变化,需要了解这一变化的类就会比较少。

3、过渡设计/设计不足

3.1、代码设计初衷

几个珍藏已久的开发技巧,这一波全部分享给你_第3张图片

应用设计模式只是方法,最终的目的,也就是初心,是提高代码的质量。具体点说就是,提高代码的可读性、可扩展性、可维护性等。所有的设计都是围绕着这个初心来做的。

在做代码设计的时候,你一定要先问下自己,为什么要这样设计,为什么要应用这种设计模式,这样做是否能真正地提高代码质量,能提高代码质量的哪些方面

掌握了设计原则和思想,我们能更清楚地了解为什么要用某种设计模式,就能更恰到好处地应用设计模式,甚至我们还可以自己创造出来新的设计模式。

3.2、代码设计过程

几个珍藏已久的开发技巧,这一波全部分享给你_第4张图片

我们先要去分析代码存在的痛点,比如可读性不好、可扩展性不好等等,然后再针对性地利用设计模式去改善,而不是看到某个场景之后,觉得跟之前在某本书中看到的某个设计模式的应用场景很相似,就套用上去,也不考虑到底合不合适,最后如果有人问起了,就再找几个不痛不痒、很不具体的伪需求来搪塞,比如提高了代码的扩展性、满足了开闭原则等等。

最重要的是养成分析问题、解决问题的能力。这样,看到某段代码之后,你就能够自己分析得头头是道,说出它好的地方、不好的地方,为什么好、为什么不好,不好的如何改善,可以应用哪种设计模式,应用了之后有哪些副作用要控制等等。

3.3、应用场景

几个珍藏已久的开发技巧,这一波全部分享给你_第5张图片

设计模式要干的事情就是解耦,也就是利用更好的代码结构将一大坨代码拆分成职责更单一的小类,让其满足高内聚低耦合等特性;

如果参与的只是一个简单的项目,代码量不多,开发人员也不多,那简单的问题用简单的解决方案就好,不要引入过于复杂的设计模式,将简单问题复杂化。

3.4、持续重构

几个珍藏已久的开发技巧,这一波全部分享给你_第6张图片

持续重构不仅仅是保证代码质量的重要手段,也是避免过度设计的有效方法。在真正有痛点的时候,再去考虑用设计模式来解决,而不是一开始就为不一定实现的未来需求而应用设计模式。

3.5、设计不足

  • 理论知识的储备:熟练掌握各种设计原则、思想、编码规范、设计模式。理论知识是解决问题的工具,是前人智慧的结晶;

  • 刻意训练:理论知识都学过,但是很容易忘记,遇到问题也想不到对应的知识点。实际上,这就是缺乏理论结合实践的刻意训练;

  • 代码质量、设计意识:在写代码之前,要多想想未来会有哪些扩展的需求,哪部分是会变的,哪部分是不变的,这样写会不会导致之后添加新的功能比较困难,代码的可读性好不好等代码质量问题。

3.6、代码场景化

设计是一个非常主观的事情,不夸张地讲,可以称之为一门“艺术”。那相应地,好坏就很难评判了。如果真的要评判,我们要放到具体的场景中。脱离具体的场景去谈论设计是否合理,都是空谈。这就像我们经常说的,脱离业务谈架构都是“耍流氓”。

开发的是偏底层的、框架类的、通用的代码,那代码质量就比较重要,因为一旦出现问题或者代码改动,影响面就比较大。

4、复杂项目设计思想

4.1、封装与抽象

抽象和封装能有效控制代码复杂性的蔓延,将复杂性封装在局部代码中,隔离实现的易变性,提供简单、统一的访问接口,让其他模块来使用,其他模块基于抽象的接口而非具体的实现编程,代码会更加稳定。

4.2、分层与模块化

几个珍藏已久的开发技巧,这一波全部分享给你_第7张图片

每一层都对上层封装实现细节,暴露抽象的接口来调用。而且,任意一层都可以被重新实现,不会影响到其他层的代码。

面对复杂系统的开发,我们要善于应用分层技术,把容易复用、跟具体业务关系不大的代码,尽量下沉到下层,把容易变动、跟具体业务强相关的代码,尽量上移到上层

4.3、基于接口通信

几个珍藏已久的开发技巧,这一波全部分享给你_第8张图片

一般来讲都是通过接口调用。在设计模块(module)或者层(layer)要暴露的接口的时候,我们要学会隐藏实现,接口从命名到定义都要抽象一些,尽量少涉及具体的实现细节。

4.4、高内聚、松耦合

几个珍藏已久的开发技巧,这一波全部分享给你_第9张图片

高内聚、松耦合是一个比较通用的设计思想,内聚性好、耦合少的代码,能让我们在修改或者阅读代码的时候,聚集到在一个小范围的模块或者类中,不需要了解太多其他模块或类的代码,让我们的焦点不至于太发散,也就降低了阅读和修改代码的难度;

4.5、为扩展而设计

几个珍藏已久的开发技巧,这一波全部分享给你_第10张图片

越是复杂项目,越要在前期设计上多花点时间。提前思考项目中未来可能会有哪些功能需要扩展,提前预留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构的情况下,轻松地添加新功能。

除了满足开闭原则,做到代码可扩展,我们前面也提到很多方法,比如封装和抽象,基于接口编程等。识别出代码可变部分和不可变部分,将可变部分封装起来,隔离变化,提供抽象化的不可变接口,供上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。

4.6、KISS首要原则

几个珍藏已久的开发技巧,这一波全部分享给你_第11张图片

简单清晰、可读性好,是任何大型软件开发要遵循的首要原则。只要可读性好,即便扩展性不好,顶多就是多花点时间、多改动几行代码的事情。但是,如果可读性不好,连看都看不懂,那就不是多花时间可以解决得了的了。

不管是自己还是团队,在参与大型项目开发的时候,要尽量避免过度设计、过早优化,在扩展性和可读性有冲突的时候,或者在两者之间权衡,模棱两可的时候,应该选择遵循 KISS 原则,首选可读性。

4.7、遵守代码规范

几个珍藏已久的开发技巧,这一波全部分享给你_第12张图片

遵从统一的编码规范,所有的代码都像一个人写出来的,能有效地减少阅读干扰。在大型软件开发中,参与开发的人员很多,如果每个人都按照自己的编码习惯来写代码,那整个项目的代码风格就会千奇百怪,这个类是这种编码风格,另一个类又是另外一种风格。在阅读的时候,我们要不停地切换去适应不同的编码风格,可读性就变差了。所以,对于大型项目的开发来说,我们要特别重视遵守统一的开发规范。

5、代码质量

5.1、代码规范

几个珍藏已久的开发技巧,这一波全部分享给你_第13张图片

编码规范不难掌握,关键是要严格执行。在 Code Review 时,我们一定要严格要求,看到不符合规范的代码,一定要指出并要求修改。

5.2、单测质量

几个珍藏已久的开发技巧,这一波全部分享给你_第14张图片

单元测试是最容易执行且对提高代码质量见效最快的方法之一。高质量的单元测试不仅仅要求测试覆盖率要高,还要求测试的全面性,除了测试正常逻辑的执行之外,还要重点、全面地测试异常下的执行情况。毕竟代码出问题的地方大部分都发生在异常、边界条件下。

5.3、Code Review

几个珍藏已久的开发技巧,这一波全部分享给你_第15张图片

所以,在业务开发任务繁重的时候,Code Review 往往会流于形式、虎头蛇尾,效果确实不怎么好。想真正发挥 Code Review 的作用,关键还是要执行到位,不能流于形式。

5.4、基于文档开发

几个珍藏已久的开发技巧,这一波全部分享给你_第16张图片

对大部分工程师来说,编写技术文档是件挺让人“反感”的事情。一般来讲,在开发某个系统或者重要模块或者功能之前,我们应该先写技术文档,然后,发送给同组或者相关同事审查,在审查没有问题的情况下再开发。这样能够保证事先达成共识,开发出来的东西不至于走样。而且,当开发完成之后,进行 Code Review 的时候,代码审查者通过阅读开发文档,也可以快速理解代码。

对于团队和公司来讲,文档是重要的财富。对新人熟悉代码或任务的交接等,技术文档很有帮助。而且,作为一个规范化的技术团队,技术文档是一种摒弃作坊式开发和个人英雄主义的有效方法,是保证团队有效协作的途径。

5.5、持续重构

几个珍藏已久的开发技巧,这一波全部分享给你_第17张图片

优秀的代码或架构不是一开始就能设计好的,就像优秀的公司或产品也都是迭代出来的。我们无法 100% 预见未来的需求,也没有足够的精力、时间、资源为遥远的未来买单。所以,随着系统的演进,重构是不可避免的。特别是一些业务开发团队,有时候为了快速完成一个业务需求,只追求速度,到处 hard code,在完全不考虑非功能性需求、代码质量的情况下,堆砌烂代码。

5.6、项目团队优化

几个珍藏已久的开发技巧,这一波全部分享给你_第18张图片

面对大型复杂项目,我们不仅仅需要对代码进行拆分,还需要对研发团队进行拆分。我们也可以把大团队拆成几个小团队。每个小团队对应负责一个小的项目(模块、微服务等),这样每个团队负责的项目包含的代码都不至于很多,也不至于出现代码质量太差无法维护的情况。

扫码上车

几个珍藏已久的开发技巧,这一波全部分享给你_第19张图片

喜欢就点个在看再走吧

你可能感兴趣的:(几个珍藏已久的开发技巧,这一波全部分享给你)