程序员修炼之道(通俗版)——第五章

本文探讨了编程中的几个关键概念,包括降低代码耦合度的方法、遵循迪米特法则的重要性、提高代码可配置性和利用元数据驱动应用程序的策略,以及如何处理并发编程中的时间耦合问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《程序员修炼之道》这本书中的内容挺不错,里面包含了很多精华,但一些句子很拗口,所以我就根据国人的阅读习惯,在不改变原意的情况下对词句稍加修改,标题中的“通俗版”就是这么来的。

1、假定你在编写一个类,生成科学记录仪数据图。你的数据记录仪分散在世界各地:每个记录仪对象都含有一个地点对象,给出其位置及时区。你想要让你的用户选择记录仪,绘制其数据,并标上正确的时区。你可以编写:

public void plotData(Date aDate, Selection aSelection){
    TimeZone tz = aSelection.getRecorder().getLocation().getTimeZone();
    ......
}

现在绘制例程不必要地与三个类耦合在一起——Selection、Recorder及Location。这种编码风格极大地增加了绘制例程所依赖的类的数目。这为何是一件坏事?因为它增加了系统某个地方的一个改动会影响其他代码的风险。例如,如果Fred对Location作出改动,使它不再直接包含TimeZone,你也必须改动你的代码。
应该直接要求提供你所需的东西,而不是自行“挖通”调用层次:

public void plotDate(Date aDate, TimeZone aTz){
    ......
}
plotDate(someDate, someSelection.getTimeZone());

有许多不必要的依赖关系的系统非常难以维护,往往高度地不稳定。为了使依赖关系保持最少,我们将使用迪米特法则设计我们的方法和函数。

2、函数的德墨忒尔法则(迪米特法则)规定:某个对象的任何方法都应该只调用一下情形的方法

class Demeter{
    private A a;
    private int func(){
        ......
    }
    public void example(B b){
        ......
    }
    void Demeter(B b){
        int f = func();//类内部的方法
        b.invert();//传入该方法的任何参数所包含的方法
        a = new A();
        a.setActive();//内部实例变量所包含的方法
        C c = new C();
        c.setContent();//创建的任何对象所包含的方法
    }
}

遵循迪米特法则将使你的代码适应性更好、更健壮,但也有代价:作为“总承包人”,你的模块必须直接委托并管理全部子承包人,而不牵涉到你的客户。在实践中,这意味着你将要编写大量包装方法,它们只是把请求转发给被委托者。这些包装方法既会带来运行时代价,也会带来空间开销。在有些应用中,这可能会有重大影响,甚至会让你无法接受。
与任何技术一样,你必须平衡特定应用的各种正面因素和负面因素。在数据库scheme设计中,常常会为了性能而对scheme进行“反规范化”:违反规范化原则,以换取速度。在这里也可进行类似的折衷。事实上,通过反转迪米特法则,使若干模块紧密耦合,你可以获得重大的性能改进。只要对于那些被耦合在一起的模块而言,这是众所周知的和可以接受的,你的设计就没有问题。

3、细节会弄乱我们整洁的代码,特别是如果它们经常变化。每当我们必须去改动代码,以适应商业逻辑、法律或管理人员一时的口味变化时,我们都有破坏系统或引入新bug的危险,所以我们应该让我们的系统变得高度可配置。不仅是像屏幕颜色和提示文本这样的事物,而且也包括诸如算法、数据库产品、中间件技术和用户界面风格之类更深层面的选择。这些选择应该作为配置项,而不是通过集成或重新编译来实现。

4、元数据是关于数据的数据,它是可以对任何应用进行描述的数据——应该怎样运行、应该使用什么资源等等。假定你点击某个选项,隐藏你的Web浏览器上的工具栏,浏览器将把该偏好作为元数据存储在某种内部数据库中。
但我们不只是想把元数据用于简单的偏好,我们想要尽可能多地通过元数据配置和驱动应用。我们的目标是以声明方式思考(规定做什么,而不是怎么做),并创建高度灵活和可适应的程序。我们通过采用一条一般规则来做到这一点:为一般情况编写程序,把具体情况放在别处——在编译的代码库之外。

5、我们可以在两个方面考虑时间耦合:并发(多种事情在同一时间发生)和次序(多种事情在不同时间发生的先后顺序)。当我们设计架构或编写程序时,事情往往是线性的。那是大多数人的思考方式——先做这个,再做那个。我们也要考虑各模块之间的逻辑关系,哪个排在前面哪个排在后面要理清楚。

6、编写线性代码,我们很容易做出一些假定,把我们引向不整洁的编程。但并发迫使你更仔细地对事情进行思考——这不再是你一个人的舞台。因为现在事情可能会在“同一时间”发生,你可能会突然看到某些关于时间的依赖关系。所以必须对任何全局变量或静态变量加以保护,使其免于并发访问。现在可以问问自己最初为何需要全局变量。
假定你有一个窗口子系统,其中的widget是先创建,再显示在屏幕上,分两步进行。在其显示出来之前,你不能设置widget中的状态。根据代码的设置方式,你可能会依靠这样一个事实:在你将其显示在屏幕上之前,其他对象都不会使用自己创建的widget。
但这在并发系统中可能不会如你所愿,在被调用时,对象必须总是处在有效的状态中,而且它们可能会在最尴尬的时候被调用。你必须确保,在任何可能被调用的时刻,对象都处在有效的状态中。

7、尽管在典型情况下,MVC是在GUI开发的语境中教授的,它其实是一种通用的编程技术。视图是对模型(也许是其子集)的一种解释——它无需是图形化的。控制器更是一种协调机制,不一定要与任何种类的输入设备有关。
模型:表示目标对象的抽象数据模型。模型对任何视图或控制器都没有直接的了解。
视图:解释模型的方式。它订阅模型中的变化和来自控制器的逻辑事件。
控制器:控制视图、并向模型提供新数据的途径。它既向模型、也向视图发布事件。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

changuncle

若恰好帮到您,请随心打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值