我总是看到抽象是OO为管理代码库提供的非常有用的功能。但是如何管理大型非OO代码库?还是那些最终变成了“ 泥浆大球 ”?
更新:
似乎每个人都在认为“抽象”只是模块化或数据隐藏。但是恕我直言,这也意味着必须使用“抽象类”或“接口”,这对于依赖注入和测试是必须的。非OO代码库如何管理这一点?而且,除了抽象之外,封装还有助于管理大型代码库,因为它定义并限制了数据与函数之间的关系。
使用C,非常有可能编写伪OO代码。我对其他非OO语言了解不多。那么,这是管理大型C代码库的方式吗?
我总是看到抽象是OO为管理代码库提供的非常有用的功能。但是如何管理大型非OO代码库?还是那些最终变成了“ 泥浆大球 ”?
更新:
似乎每个人都在认为“抽象”只是模块化或数据隐藏。但是恕我直言,这也意味着必须使用“抽象类”或“接口”,这对于依赖注入和测试是必须的。非OO代码库如何管理这一点?而且,除了抽象之外,封装还有助于管理大型代码库,因为它定义并限制了数据与函数之间的关系。
使用C,非常有可能编写伪OO代码。我对其他非OO语言了解不多。那么,这是管理大型C代码库的方式吗?
Answers:
您似乎认为OOP是实现抽象的唯一方法。
虽然OOP当然非常擅长于此,但这绝不是唯一的方法。大型项目也可以通过不妥协的模块化(只需看看Perl或Python,两者都擅长于此,而ML和Haskell等功能语言也是如此)以及使用诸如模板(在C ++中)的机制,就可以保持可管理性。
static
附加访问修饰符。
模块化原理不限于面向对象的语言。
实际上,由于维护此类系统的人员已经进行了一段时间(出于愤世嫉俗的态度是工作保障),因此很少进行更改(例如,考虑社会保障退休计算)和/或根深蒂固的知识。
更好的解决方案是可重复验证,我的意思是自动测试(例如单元测试)和遵循规定步骤的人工测试(例如回归测试)“而不是四处查看,看看有什么突破”。
为了开始使用现有代码库进行某种类型的自动化测试,我建议阅读Michael Feather的“有效使用遗留代码”,其中详细介绍了将现有代码库带入某种可重复测试框架OO的方法。这导致了其他人已经回答的想法,例如模块化,但是这本书描述了在不破坏事物的前提下正确的做法。
尽管基于接口或抽象类的依赖注入是进行测试的一种很好的方法,但这不是必需的。别忘了几乎所有语言都具有函数指针或eval,它们可以执行您可以使用接口或抽象类做的任何事情(问题是它们可以做更多的事情,包括许多不好的事情,而它们却不能做到)本身提供元数据)。这样的程序实际上可以通过这些机制实现依赖注入。
我发现严格使用元数据非常有帮助。在OO语言中,代码位之间的关系(通过某种程度)由类结构定义,以一种足以标准化的方式来实现,例如具有反射API。在程序语言中,自己发明那些会有所帮助。
我还发现代码生成在过程语言(与面向对象的语言相比)中更为有用。这保证了元数据与代码同步(因为它是用于生成元数据的),并为您提供了一些类似于面向方面的编程的切入点的东西-您可以在需要时插入代码的地方。有时,这是在我可以确定的环境中进行DRY编程的唯一方法。
管理代码的一种方法是按照MVC(模型-视图-控制器)体系结构将其分解为以下类型的代码。
这种代码组织方法适用于用任何OO或非OO语言编写的软件,因为通用设计模式通常对于每个领域都是通用的。同样,除了算法之外,这些类型的代码边界通常是最松散的耦合,因为它们将数据格式从输入链接到模型,然后链接到输出。
系统演进通常采取让您的软件处理更多种输入或更多种输出的形式,但是模型和视图是相同的,控制器的行为也非常相似。或者,随着时间的流逝,即使输入,模型,算法相同且控制器和视图相似,系统仍可能需要支持越来越多的不同种类的输出。或者可以扩展系统以为相同的一组输入,相似的输出和相似的视图添加新的模型和算法。
OO编程使代码组织变得困难的一种方式是,因为有些类与持久性数据结构紧密地联系在一起,而有些则没有。如果持久性数据结构与级联的1:N关系或m:n关系密切相关,则很难确定类边界,直到您在对系统进行了编码之前就已经对其进行了有意义的编码。当持久性数据的架构发生变化时,与持久性数据结构相关的任何类都将难以发展。处理算法,格式化和解析的类不太可能受到持久性数据结构的模式更改的影响。使用MVC类型的代码组织可以更好地将最混乱的代码更改隔离到模型代码中。
当使用缺少内置结构和组织功能的语言(例如,如果没有名称空间,程序包,程序集等)或无法充分控制该大小的代码库时,自然而然的反应就是开发我们自己的策略来组织代码。
该组织策略可能包括与以下内容有关的标准:应在何处保存不同的文件,在某些类型的操作之前/之后需要发生的事情,命名约定和其他编码标准,以及很多“这是如何设置的” -别惹它!” 输入注释-只要它们解释原因,这些注释才有效!
由于该策略很可能最终会根据项目的特定需求(人员,技术,环境等)进行定制,因此很难提供一种千篇一律的解决方案来管理大型代码库。
因此,我认为最好的建议是采纳特定于项目的策略,并将其作为关键优先事项:记录结构,原因,进行更改的过程,进行审核以确保其得到遵守,至关重要的是:在需要更改时进行更改。
我们大多数人都熟悉重构类和方法,但是对于使用这种语言的大型代码库,需要在必要时重构组织策略本身(包括文档)。
推理与重构相同:如果您觉得系统的整体结构混乱,您将在处理系统的较小部分时出现思维障碍,并且最终会使它恶化(至少这是我的观点)它)。
注意事项也相同:使用回归测试,确保在重构出错时可以轻松还原,并进行设计以首先促进重构(否则您将不会这样做!)。
我同意,这比重构直接代码要复杂得多,并且很难从可能不理解为什么需要这样做的经理/客户那里确认/隐瞒时间,但这也是最容易导致软件腐烂的项目类型由不灵活的顶层设计引起的...
最大的技术挑战是名称空间问题。部分链接可用于解决此问题。更好的方法是使用编码标准进行设计。否则,所有符号都会变得一团糟。
Emacs是一个很好的例子:
Emacs Lisp测试使用skip-unless
并let-bind
进行功能检测和测试装置:
有时,由于缺少先决条件,因此没有必要进行测试。可能没有编译必需的Emacs功能,要测试的功能可能会调用外部二进制文件,而该外部二进制文件可能在测试计算机上不可用,您将其命名。在这种情况下,宏
skip-unless
可用于跳过测试:
(ert-deftest test-dbus ()
"A test that checks D-BUS functionality."
(skip-unless (featurep 'dbusbind))
...)
运行测试的结果不应取决于环境的当前状态,并且每个测试应使其环境保持其在其所在状态中的状态。尤其是,测试不应取决于任何Emacs定制变量或钩子,并且如果必须对Emacs的状态或Emacs外部状态(例如文件系统)进行任何更改,则无论更改是通过还是失败,都应在返回之前撤消这些更改。
测试不应依赖于环境,因为任何此类依赖关系都可能使测试变脆或导致仅在某些情况下发生且难以复制的故障。当然,被测代码可能具有影响其行为的设置。在这种情况下,最好对
let-bind
所有此类设置变量进行测试,以在测试期间设置特定的配置。该测试还可以设置许多不同的配置,并对每个配置运行被测代码。
和SQLite一样。这是它的设计:
sqlite3_open()→打开与新的或现有的SQLite数据库的连接。sqlite3的构造函数。
sqlite3→数据库连接对象。由sqlite3_open()创建并由sqlite3_close()销毁。
sqlite3_stmt→准备好的语句对象。由sqlite3_prepare()创建,并由sqlite3_finalize()销毁。
sqlite3_prepare()→将SQL文本编译为字节代码,它将完成查询或更新数据库的工作。sqlite3_stmt的构造函数。
sqlite3_bind()→将应用程序数据存储到原始SQL的参数中。
sqlite3_step()→将sqlite3_stmt前进到下一个结果行或完成。
sqlite3_column()→sqlite3_stmt在当前结果行中的列值。
sqlite3_finalize()→sqlite3_stmt的析构函数。
sqlite3_exec()→一个包装函数,对一个或多个SQL语句字符串执行sqlite3_prepare(),sqlite3_step(),sqlite3_column()和sqlite3_finalize()。
sqlite3_close()→sqlite3的析构函数。
令牌生成器,解析器和代码生成器组件用于处理SQL语句,并将其转换为虚拟机语言或字节码的可执行程序。粗略地说,这三层实现了sqlite3_prepare_v2()。前三层生成的字节码是准备好的语句。虚拟机模块负责运行SQL语句字节代码。B-Tree模块将数据库文件组织到具有有序键和对数性能的多个键/值存储中。Pager模块负责将数据库文件的页面加载到内存中,以实现和控制事务,并创建和维护日记文件,以防止崩溃或电源故障后数据库损坏。OS Interface是一个瘦的抽象,提供了一组通用的例程,以使SQLite适应在不同的操作系统上运行。粗略地说,最下面的四层实现sqlite3_step()。
虚拟表是在打开的SQLite数据库连接中注册的对象。从SQL语句的角度来看,虚拟表对象看起来像任何其他表或视图。但是在后台,对虚拟表的查询和更新将调用虚拟表对象的回调方法,而不是对数据库文件进行读写。
虚拟表可能表示内存中的数据结构。或者它可能代表磁盘上非SQLite格式的数据视图。或者,应用程序可以按需计算虚拟表的内容。
这是虚拟表的一些现有和假定用途:
全文搜索界面 使用R树的空间索引 内省SQLite数据库文件(dbstat虚拟表)的磁盘内容 读取和/或写入逗号分隔值(CSV)文件的内容 像访问数据库表一样访问主机的文件系统 启用R等统计数据包中数据的SQL操作
SQLite使用多种测试技术,包括:
三种独立开发的测试工具 部署后的配置中100%的分支测试覆盖率 数百万个测试案例 内存不足测试 I / O错误测试 碰撞和断电测试 模糊测试 边值测试 禁用的优化测试 回归测试 格式错误的数据库测试 广泛使用assert()和运行时检查 Valgrind分析 未定义的行为检查 检查清单
参考文献