C和C++语言是流行和常用的依赖于顺序的语言。C++略为有点改进,在类范围内部不依赖于顺序。依赖于顺序是一种惩罚,是一种不重视程序员感受的表现,并且在深层次要求采用采用声明和实现分开,因而也就不能维持DRY原则。违背DRY会导致维护复杂。
我详细说明一下为什么依赖顺序是罪恶的。
大家都知道抽象是一种强大的能力,但是,依赖于顺序强迫我们只能从低层次的细节开始,这跟现如今流行的IDE的智能完成一样。我们不得不把所有的细节描述完备,才能构造更大的构件。
我还是用一个实例来说明吧。
我为了练习的目的,试图写一个HTTP Client,自然,我是从阅读RFC规范开始的。通过扫描RFC的目录和快速掠过其内容,我们发现HTTP是一个请求响应模式的简单协议,其请求和响应都是所谓的Message,显然,只要我们能解析和构造这个Message,我们就大致完成任务。
于是就有了:
namespace Http
{
class Message
{
public:
MessageHeader headers;
CRLF crlf;
MessageBody body;
std::string toString(void)
{
return headers.toString() + crlf.toString() + body.toString();
}
};
class MessageHeader
{
public:
std::vector<Field> fields;
std::string toString(void)
{
std::string result;
for (int i = 0; i < fields.size(); ++i)
{
result += fields[i].toString();
}
return result;
}
};
class Field
{
public:
FieldName name;
Colon colon;
FieldValue value;
CRLF crlf;
std::string toString(void)
{
return name.toString() + colon.toString() + value.toString() + crlf.toString();
}
};
class FieldName
{
public:
FieldNameValue name;
std::string toString(void)
{
std::string result;
switch (name)
{
case CacheControl:
result = "Cache-Control";
break;
......
}
return result;
}
FieldNameValue fromString(std::string fieldName)
{
FieldNameValue result;
if (fieldName == "Cache-Control")
result = CacheControl;
else if (...)
...
return result;
}
};
enum FieldNameValue
{
//generic headers
CacheControl,
Connection,
Date,
Pragma,
......
}
我想,上面的代码非常的直接了当。几乎不需要解释,类似于递归下降法制作解析器,而事实上,确实是下降的,但是递归没有,因为HTTP的Message格式还没有那么强大和灵活。稍微有点奇特的是FieldName和FieldNameValue这一对,它们合作构成了我设想的枚举,这也说明了C/C++语言的枚举并不强大到可以作为一个抽象机制使用,当然,这不是我们焦点。暂且不论。
由于其中的toString都是很简单的,所以我准备把它们做成内联函数,这个无可厚非,我们看看就是这个要求是怎么导致我们需要扭曲我们的代码的。
首先需要说明的是:上面的代码不能通过C++编译器的编译。原因很简单:编译器看到MessageHeader被使用的时候还没有发现其定义,会给出一大堆抱怨。怎么办?我们给出一个前向声明吧。也很简单,在使用之前声明一下就行了:class MessageHeader。把它加在使用之前的任意地方,编译器就应该不说话了吧。
哦,还是不行,原因是:对了,它要求必须有完整的类定义才能定义该类的对象,别忘了,C++是值语义的语言。好吧,我们改造成指针……,类型后面加上*,一连串的.变成->,应该没有问题了吧(如果你做过实际的项目,你就知道:这个修改的代价是非常高的,不过区分指针和原始对象不是我们批判的焦点,这里不再深入)。啊……还是不行,原因仍然很简单,就算可以定义指针而不是真正的对象,我们仍然不不能够调用该指针对象的方法,原因是:我(编译器)没有看到类型的定义,怎么知道有没有该方法可以调用呢?
现在我们的选择有三个,但是都不够优雅。
一、我们调整类型定义的顺序,把低层次的构件往前挪。
二、我们把成员函数的实现挪到最后,这时候它已经看到所有的定义了。
三、这是二的彻底化,干脆移动其实现到另一个文件。
方法一的缺陷就是,我们不再能够自然的从上而下看出我们设计的脉络。这跟我看HTTP的RFC一个缺陷是一样的,HTTP的RFC就不是采用自上而下描述的,而是先描述了一些基础的构件,在描述了整体结构,看得人晕头转向,另外,协议中有大量的用途和功能分析,我不是说这不好,但是作为协议这让人分神。可以把功能或者需求放在开始诱导大家构思HTTP应该怎样设计。而不是放在HTTP协议都描述了以后才描述功能。
方法二和方法三本质上是一样的,但是方法三的可维护性就更差一些了,因为所有的修改都得涉及到两个文件,而且,方法三不再能够是inline的,这个代价让我觉得非常不快。而方法二确实仍保持inline,但是,一旦修改我还是得从头到尾的查找。让我写两遍相同的函数头也让我不爽。
而最本质的原因就是:我没有办法让一个决策(函数签名)在一个地方说明,必须出现在两个地方,这是对DRY或者叫做SPOT原则的公然违反,它们会导致大量的微妙的问题和产生难以维护的代码。
C++在很大程度上鼓励我们采用第三种方案,这也是C++中的正统方案,只要我们坚持顺序依赖,我们就不得不倾向于这种方案。顺序依赖把维持依赖关系的责任推给了程序员而不是编译器自己去解决,这当然简化了编译器,但是却使得程序员的日子更艰难。那么,顺序依赖究竟应该是谁的责任呢?是该由程序员处理的呢,还是该由编译器处理的?
我们先大而化之描述一下依赖。依赖表示一个构件(这儿的构件包括函数,类,全局变量,源文件等等任意可以用来搭建整个系统的部分)对别的构件的使用。我们很自然很直观的能感受到,只要我们使用了别的构件,编译器显然知道我们的构件依赖于那些使用的构件,所以,让程序员维持依赖是不必要的。
但是还有一个问题:构件A依赖于构件B,这个编译器知道,但是构件B在哪儿?这个编译器不知道。显然,我们需要而且也只需要在某个地方描述一下这个构件跟位置的映射,我们的编译器就可以工作了。如果我们把这种映射放在任何构件里面,这样势必会导致这种映射关系描述上的重复,导致大量的维护问题。显然,最适合描述构件跟位置的映射关系的地方是工程的配置文件。它是整个工程级别的属性。
那么顺序依赖是什么呢?顺序依赖本质上是希望把构件和位置的映射关系消除。它要求所有依赖的东西必须在它之前出现,这样,被依赖方也就自然不需要指定位置了,它就在前面。这个有利于分块编译(不知道大家注意没有,分块编译的用处似乎并不是很大,无论是大工程还是小工程,因为:毕竟编译只是一个阶段,而且维持构件是否是最新编译版本似乎也一点都不难),但是实现分块编译导致需要大量的重复编译,毕竟任何被多次依赖的组件都必须多次被编译。并且分块编译要求类似于Makefile之类的支持环境,现代的语言及其开发环境都完全不支持分块编译了,它们都采用运行时动态装载这个更灵活的模式。
分享到:
相关推荐
1. 人体各器官功能的生长发育一般遵循由低级到高级、由简单到复杂、由粗到细、由近到远的顺序,这些规律对于护理实践中理解病人的生理变化至关重要。 2. 社会评估内容涉及情绪状态、人格类型、认知能力、应对能力和...
顺序:一系列按特定顺序排列的事物。 14. **shallow** - 浅的:形容水或其他物体深度不大。 15. **shiver** - 发抖:因寒冷、恐惧或疾病引起的颤抖。 16. **shrug** - 耸肩:一种表达不确定或不在乎的动作。 17. **...
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
《基于YOLOv8的智慧社区独居老人生命体征监测系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计
Android Studio Meerkat 2024.3.1 Patch 1(android-studio-2024.3.1.14-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/90557060 part2: https://download.csdn.net/download/weixin_43800734/90557056
侧轴承杯加工工艺编制及夹具设计.zip
NASA数据集锂电池容量特征提取(Matlab完整源码和数据) 作者介绍:机器学习之心,博客专家认证,机器学习领域创作者,2023博客之星TOP50,主做机器学习和深度学习时序、回归、分类、聚类和降维等程序设计和案例分析,文章底部有博主联系方式。从事Matlab、Python算法仿真工作8年,更多仿真源码、数据集定制私信。
板料折弯机液压系统设计.zip
C6150车床的设计.zip
机器学习之KNN实现手写数字
python爬虫;智能切换策略,反爬检测机制
mpls-vpn-optionA-all
56tgyhujikolp[
GB 6442-86企业职工伤亡事故调查分析规则.pdf
汽车液压式主动悬架系统的设计().zip
2000-2024年各省专利侵权案件结案数数据 1、时间:2000-2024年 2、来源:国家知识产权J 3、指标:专利侵权案件结案数 4、范围:31省 5、用途:可用于衡量知识产权保护水平
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
内容概要:本文档详细复现了金融数学课程作业,涵盖欧式看涨期权定价和投资组合优化两大部分。对于欧式看涨期权定价,分别采用Black-Scholes模型和蒙特卡洛方法进行了计算,并对彩虹期权进行了基于最大值的看涨期权定价。投资组合优化部分则探讨了最小方差组合、给定收益的最小方差组合、最大效用组合以及给定风险的最大收益组合四种情形,还对比了拉格朗日乘数法和二次规划求解器两种方法。文中不仅提供了详细的MATLAB代码,还有详尽的中文解释,确保每一步骤清晰明了。 适合人群:金融工程专业学生、量化分析师、金融数学爱好者。 使用场景及目标:①帮助学生理解和掌握金融衍生品定价的基本原理和方法;②为从事量化分析的专业人士提供实用工具和技术支持;③作为教学材料辅助高校教师讲授相关内容。 其他说明:文档还包括了完整的论文结构建议,从封面页到结论,再到附录,涵盖了所有必要元素,确保提交的作业符合学术规范。此外,还特别强调了数据预处理步骤,确保代码可以顺利运行。
脉冲电解射流加工喷射装置设计(1)
ThinkPad S1 (2nd Generation) 和ThinkPad Yoga 260 用户指南V3.0,包含如何拆机更换硬件