前一阵子之所以有一定的牢骚,主要是因为最近总能遇到一股Destruct的力量。什么是Destruct呢,就是construct的反义词呗。第一次对这个词感兴趣,是在一篇说微软和对手竞争的手段:对一个既有的不属于自己的标准,创造出一种跟他很像,但又不是一回事的标准。此过程就是对既有标准的一种破坏,就是Destruct。相信大家都知道我说的是什么事情了吧,就是当时的Java标准之争。
这种自行创造出一套边缘体系,与现有的体系平行却完全不受控制的东西,都是极具破坏性的。标准如此,开发也如此。最近碰到的就是这么一种破坏,能够把人搞得狠抓狂。
比如说吧,有一个配置相关的类,该类对一堆xml做解析并转换成配置对象。这堆配置文件当中有一个缺省配置,所以其他的配置里面没有的描述,都相当于直接使用缺省配置。这个xml的结构本身有一个规定,即:所有开关量、数值等,写在配置节点的属性(attribute)当中;所有需要显示出来的Html,写在一个子节点当中。具体一点的例子是:
<UserRole name="$default" canCreate="false" canView="true" enabled="true">
<Description><![CDATA[这是一个<b>未知用户</b>!]]></Description>
<Hello><![CDATA[系统出错,请联系管理员!]]></Hello>
</UserRole>
<UserRole name="user">
<Description><![CDATA[这是一个<b>普通用户</b>!]]></Description>
<Hello><![CDATA[{name},您好!]]></Hello>
</UserRole>
<UserRole name="admin" canCreate="true">
<Description><![CDATA[这是一个<b>管理员</b>!]]></Description>
</UserRole>
在这么一个系统下面,假设我们有如下代码:
UserRole role = user.Role;
Debug.WriteLine(role.CanCreate);
Debug.WriteLine(role.Hello);
我们可以预期,如果是普通用户,那会返回"false"和"{name},您好!",而如果是管理员,则分别返回"true"和"{name},您好!"。这么设计的最基本原因,是为了可以很方便的修改一些默认的配置,例如欢迎语。可以想象,如果我们修改$default的Hello字节点,就可以很方便的更新大多数用户类型的显示方式,同时也不会影响到那些有特殊显示要求的用户类型(比如说老总可能喜欢看"{name},恭喜发财啊!")。
稍微思考一下就会发现,如果在子节点上定义属性,现有的体系是绝对不支持“缺省配置”的。即$default配置上面的这些属性是不会被其他配置对象获取到的。具一个具体的例子,有一天程序员Murderer在配置里面加了一些修改:
<UserRole name="$default" canCreate="false" canView="true" enabled="true">
<Description><![CDATA[这是一个<b>未知用户</b>!]]></Description>
<Hello><![CDATA[系统出错,请联系管理员!]]></Hello>
<Salary queryPage="http://mycorp/newpath/salaryquery.aspx" controlRight="none"><Salary>
</UserRole>
<UserRole name="user">
<Description><![CDATA[这是一个<b>普通用户</b>!]]></Description>
<Hello><![CDATA[{name},您好!]]></Hello>
<Salary queryPage="http://mycorp/oldpath/salaryquery.aspx" controlRight="view"><Salary>
</UserRole>
<UserRole name="CFO">
<Description><![CDATA[这是一个<b>财务总监</b>!]]></Description>
<Hello><![CDATA[{name},您好!]]></Hello>
<Salary queryPage="http://mycorp/oldpath/salaryquery.aspx" controlRight="view, pay"><Salary>
</UserRole>
<UserRole name="admin" canCreate="true">
<Description><![CDATA[这是一个<b>管理员</b>!]]></Description>
<Salary queryPage="http://mycorp/system/salarycontrol.aspx" controlRight="all"><Salary>
</UserRole>
这个修改可真是惨了,系统出现错误,因为配置错了,oldpath的目录不存在。其实这是该页面在旧系统里面的位置,新系统改为newpath了。原来一直使用这个配置的人会很奇怪,明明$default里面已经改过来了,而所有用户除了管理员的之外,都应该是访问同一个地方啊。为什么呢?
原因就在于子节点的属性无法继承缺省配置!而这么修改的程序员Murderer也就是图了方便:原来这个配置可能是在另外一个没有“缺省配置”特性的xml配置系统里面的,现在需要做一个合并,结果不加修改就配置过来了。同时把读取该属性的代码也一并拷贝过来,比如说:
public string SalaryPage
{
get{ return _node["Salary"].Attributes["queryPage"].Value; }
}
作为对比,原始读取Name属性的代码可能是类似这样的:
public string Name
{
get{ return GetAttribute("name"); }
}
private string GetAttribute(string attributeName)
{
XmlAttribute attribute = _node["Salary"].Attributes[attributeName];
if (attribute == null)
{
if (this != _Default) {return _Default.GetAttribute(attributeName);} else {return null;}
}
else
{
return attribute.Value;
}
}
可以想象,其他的属性比如CanCreate、CanView也应该是类似的,属以同一个体系,具有同样特性的东西(比如有缺省配置的特性)。而由于SalaryPage的加入,导致了整个体系的崩解——那些原有的代码似乎没有了存在的意义。原因很简单,这些原有的代码之所以存在,就是为了保证:
1、每一个配置可以不用配置所有的内容,每一项内容不需要特别指定就有一个默认的行为;
2、当需要修改某项内容的默认行为,只需要修改默认配置即可,无需对每一个配置进行修改;
3、尽可能使代码能够得到重复利用,而不需要为了每一个新增加的配置项,写太多重复性的代码。(这里没有很好的体现,实际上对每一个配置,很可能还有一些诸如安全性、正确性、完整性等方面的检查代码,这些代码可能会写在GetAttribute当中。)
然后由于SalaryPage属性代码,以及其在xml当中的表现形式,导致整个配置无法达到原来设计的目的:
1、你无法保证Salary节点的内容有默认行为,也无法保证日后还会不会有其他类似的东西;
2、你无法修改默认行为,因为已经没有默认行为这一说;
3、代码也不再是重用的,进一步说就是无法保证每一个属性在诸如安全性、正确性、完整性等各个方面的标准是一致的。
今天导致我立即抓狂的是,突然发现系统在某一种情况下出错,调试后发现原来是类似Salary的地方,有某些配置不正确(实际上就是“默认配置”错了)。结果写这个东西的人跟我说,你每一个配置都打开来看一遍吧。听了我就狂晕,要这样当初设计这个配置系统干嘛?这个系统本身花了工夫去写出来就是为了避免这样的问题,现在又Fallback回去了……
至于其他的问题,我想都不敢想了,虽然没有遇到。
本来这样的问题不应该发生:
1、要么按照系统的设计,添加到<UserRole salaryQueryPage="...">当中;
2、要么xml还是按照现有的方式修改,但是代码也要遵从原有设计的意图,修改成字节点的属性也是能够有默认行为的。
实际上这样的修改很难吗?一点也不难。这样的修改很费时间吗?也许费一点时间。但绝对不会比发生错误之后,调试半天最后让我每一个配置都查一遍来的要少!可是怎么说都还是会有人不明白这么一个问题,即,修改错误的时间比开发的时间要长,修正这样的问题代码给开发人员所带来的痛苦,也远比开发一段代码要大。这就是现实,遇到这样的现实多了,你就只好举手投降。他们没有哪个意识,或者上头不给他们一种痛苦以驱动他们注意这样的问题,那么你再怎么注意,事情也还是没完没了——你创造了世界,他们来毁灭。好比随地吐痰的问题:我爸有一次随地吐了口痰,我妈就说他。他说了一句:“我不吐照样有人吐,路上照样是脏的。还不如我也一起吐,大家都吐,吐得地上都是痰,市领导实在恶心不下去了顶一个不准随地吐痰大小便法,吐一口痰扣1000块钱收监3个月,你看还有没有人吐痰?!所以我不吐痰不解决问题,我吐痰倒能够解决问题。”(我添油加醋了一番,望老爸看了不会介意。)如此说法,看来也不无道理。
要是你是一个小卒,要是你也遇到了这样的情况,那么你千万不要有代码洁癖——那绝对是没完没了地事情。赶紧发挥你的智慧,把代码写的有多烂实多烂,领导总有看不下去的一天。只有到了这一天,你的痛苦才算是有解决的希望。
其实说白了,如果你那里有这样的人存在,用NH、用Atlas、用XXX都不管用,代码照样会是一团糟。使用的高级货封装得越好,问题越是严重。假设是NH,如果Murderer有一个紧急任务,他着急要实现一个什么功能,但是不太懂NH,稍微弄了一下没有弄出来,结果必然是直接在.aspx.cs上面添加一段操作数据库的代码。这堆代码直接围绕着DataTable操作,连对象都不会给你生成,也不要期待什么防范Sql注入攻击了。到时候你想改回NH,恐怕是会比我现在还要抓狂的。
从技术的角度讲,防止类似Murderer的Destruct行为,有哪些切实可行的手段?(不讨论行政手段。)
比如说TemplateMethod模式,或者多做CodeReview?老实话,我对这两者均不抱期望,要么实际效果不大,要么成本太高却只解决表面问题,下次依旧。难道只有行政手段才能够解决问题?
再次抛砖引玉,望众人多多讨论。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器