V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jatsz
V2EX  ›  编程

架构整洁之道 - 读书后记

  •  
  •   jatsz · 2020-09-08 08:06:11 +08:00 · 3843 次点击
    这是一个创建于 1566 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://book.douban.com/annotation/98612397/

    作为一个老程序员,无论接口也好,依赖倒置也好,觉得自己特别的“懂”。但是站在架构的角度为什么这么做,这本书回答的很清楚。

    书很薄,也容易读,周五下午书到家,周六下午读完了,整个周日都在思考接口和依赖倒置。

    这本书还有个好处就是清晰的告诉你编程范式就三种:结构式,函数式和面向对象。你不知道我读完这个有多放松。好比你苦苦追剧 38 集,就等 40 集的真相大白,结果无意中听到了剧透。去你妈的,这不是吊胃口么,老子不跟了。Bob 就是这个剧透。

    看完还有个思考是,按照 Clean Architecture 的设计,似乎有个假定业务是稳定的,我们外围的 Application Logic,Presentation Layer,Runtime Environment 在类似于 Onion 的结构外层。其实现实却不然,只要稍有维护经验的人都知道,简单的业务需求修改就可以跨越这些层。比如一个业务是新增业务,需要 Entity 做支撑,需要有着自己的业务逻辑和显示逻辑,包括可能需要 Environment 的 Setup 。简单说业务经常变,是不是 Clean Architecture 就不适用了?

    是,也不是。如果业务一直变的情况下,而且不能预测方向的情况下我们该怎么做?我觉得有两点我们可以做:

    • 注重模块化,谨慎抽象。给后期做一些准备。
    • 通过使用接口,让有些依赖反转,目的是推迟做决定。

    业务一直变,程序员 /架构师有办法吗?坦白讲:没有。作为程序员 /架构师我们能做些什么来改善吗?能。

    BUT, WHAT IS COST OF DIP?

    在几天的思考里还有个问题一直萦绕在我脑海里:DIP 的代价是什么?如果没有代价的话,我们可以随意的用 DIP 原则。再说没有什么事情是没有代价的。

    不知道你们有没有见到这样的项目:不管什么需求,都先要去定义和实现接口,实现和接口在接口被定义在不同的项目(部署单元)里。如果你是类似.NET 和 Java 工程师,你肯定会见到这样的项目。这样的项目开发体验如何?

    我有很多次遇到这样的项目的经历,开发和维护体验其实都及其糟糕,感觉不到任何 DIP 的好处,而是一堆神烦的约定。一个站在用例角度上的小修改,跨越好几个部署单元(DLL 或者 jar)。

    调试的时候更加让人苦恼,你无法通过反馈的问题 /日志,直接通过查阅代码来预测代码的运行行为。你对着报错却“迟迟”找不到原因。报错点和实际问题点隔上好远。每次项目里小问题,都是对项目的一次大探索。

    所以应用 DIP 的代价是什么?软件架构最重要的事情是什么?管理复杂度(Complexity Management)。减少不必要的意外复杂度(Accidental Complexity)。简单讲就是让本来的简单的事情保持简单。而 DIP 在引入隔离,推迟决定的同时也引入了复杂度:

    the DIP comes with the complexity.

    关键点是:DIP 这里引入的是必要的复杂度,还是意外的复杂度。

    给那些“尽信书”的朋友: https://naildrivin5.com/blog/2019/12/02/dependency-inversion-principle-is-a-tradeoff.html

    但是不妨碍,我给这本书 5 星。

    7 条回复    2020-09-08 12:10:02 +08:00
    rapperx2
        1
    rapperx2  
       2020-09-08 08:08:50 +08:00   ❤️ 1
    这背景 看着刺眼
    jatsz
        2
    jatsz  
    OP
       2020-09-08 08:09:57 +08:00
    @rapperx2 我也觉得,我发布到“阅读”频道就这样了,没有找地方调整呢。
    jatsz
        3
    jatsz  
    OP
       2020-09-08 08:11:06 +08:00
    @rapperx2 好像换个频道就可以了,我换到“编程”频道了。
    STRRL
        4
    STRRL  
       2020-09-08 08:40:53 +08:00 via Android
    关于业务一直变 所以开发者们需要“两顶帽子”,开发和重构。好的设计会让扩展更方便,但是重构,以及不断的修改抽象依旧不可避免。
    sikong31
        5
    sikong31  
       2020-09-08 08:58:24 +08:00
    Clean Architecture 讲的都有道理,可部分的简单业务写起来简直怀疑人生,一堆的单例注入,一层包一层,写起来感觉在套娃,跳文件有得翻
    zhazi
        6
    zhazi  
       2020-09-08 10:14:03 +08:00
    DIP 这里引入的是必要的复杂度,还是意外的复杂度。
    这里是 jhipster 针对目前 spring mvc 模式中针对 service 接口从‘技术角度’上的理解
    Should we use interfaces with our Service Beans?
    Short answer: No.

    If you want the long answer, here it is:

    One of the main interests of using Spring is AOP. This is the technology that allows Spring to add new behaviors on top of your Beans: for instance, this is how transactions or security work.

    In order to add those behaviors, Spring needs to create a proxy on your class, and there are two ways of creating a proxy:

    If your class uses an interface, Spring will use a standard mechanism provided by Java to create a dynamic proxy.
    If your class doesn’t use an interface, Spring will use CGLIB to generate a new class on the fly: this is not a standard Java mechanism, but it works as well as the standard mechanism.
    Some people will also argue that interfaces are better for writing tests, but we believe we shouldn’t modify our production code for tests, and that all the new mocking frameworks (like EasyMock) allow you to create very good unit tests without any interfaces.

    So, in the end, we find that interfaces for your Service beans are mostly useless, and that’s why we don’t recommend them (but we leave you with the option to generate them!).
    但是!
    如果仔细想想为什么使用接口进行依赖注入是一种增加复杂度方式,因为根本没有通过依赖注入获取到任何好处。
    让我们在去想想接口的特性,可以被多个类多实现,最大程度上解耦。问题就在这里了,为什么我们的接口很少被多个类实现,是我们写的 curd 更多的业务是面向过程的代码,没有针对业务进行抽象归纳总结,是 oop 编程的一种反模式。
    而书中的论点更多都是以 oop 的视角出发的。
    信不信书这件事要看自己的深度,如果你到了一个陌生的领域还是不要轻易下结论。
    jatsz
        7
    jatsz  
    OP
       2020-09-08 12:10:02 +08:00
    > 为什么我们的接口很少被多个类实现
    这也是我一直思考的,是我们的问题,还是接口的问题。依赖倒置是一种手段,这种手段有着将代码变复杂的代价。为什么要一开始就引入这个代价?为什么不在开始的时候做一些模块化,通过这些轻量级的手段将未来的改动做一定程度的限制。在:1,需要用到 DIP 的时候在引入 DIP 。2,除非已开始你就知道这个地方就需要 DIP 。

    我们在看这些讲架构书的时候,很多时候为了讨论问题的简单,会做一些前置假设。我们有时候需要看看这些前置假设是否真的成立,是否也是你的问题。别强行将别人项目的问题变成自己项目的问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2847 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 14:30 · PVG 22:30 · LAX 06:30 · JFK 09:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.