在C语言中,代码复制是否必不可少?


16

我是C的新手,我想知道在编写通用数据结构和C时,代码重复是否必不可少?

hash map例如,我可以尝试编写一个通用实现,但是我总是发现最终结果很混乱。我还可以针对此特定用例编写专门的实现,使代码清晰易读,易于调试。后者当然会导致某些代码重复。

通用实现是规范,还是针对每个用例编写不同的实现?


11
您所描述的道德困境并非C语言独有。有趣的是,我发现创建泛型实现的门槛很高,除非您有它的听众。创建良好的通用实现所需的工作量远远超过了点解决方案IME。
罗伯特·哈维

3
@RobertHarvey以我的经验,可重复使用的代码编写需要花费3-4倍的努力
gna

1
@kevincline:好吧,Java确实有泛型。当然,这是“不注意幕后的男人”类型的泛型。
罗伯特·哈维

1
在泛型之前,您可以分别传入Object和Float,Double,Integer和Long。
Daniel Kaplan 2013年

3
Deja vu是一种代码嗅觉检查,对DRY的偏爱是所有值得关注IMO的最佳实践的根本。但是我已经朝着面向OOP的语言投稿,试图为避免重复而努力。如果您没有不必要地将无关的问题彼此束缚或为了使两个相似的功能合而为一而破坏可读性,那么我会本能地使用大多数语言。
Erik Reppen 2013年

Answers:


27

C使得编写通用代码变得困难。与为您提供模板和虚函数的C ++不同,C仅具有3种编写通用代码的机制:

  1. void* 指针
  2. 预处理器宏
  3. 功能指针

void* 指针远非理想,因为您失去了编译器提供的所有类型安全性,这可能导致无效类型强制转换导致难以调试的未定义行为。

预处理程序宏具有明显的缺点-预处理程序扩展基本上只是在编译阶段之前发生的查找/替换机制,这又可能导致难以调试的错误。原型示例如下:#define add(x) (x+x)x如果调用,可以将其中的值增加两次add(i++)。您可以完全使用C-macros编写模板样式的通用代码,但是结果确实很丑陋且难以维护

函数指针提供了一种编写通用代码的好方法,但是不幸的是,它们没有为您提供类型通用性-它们仅提供了运行时多态性的可能性(这就是为什么例如标准库qsort仍然需要使用void*指针。)

您还可以使用struct在C中实现类层次结构,就像在提供通用GObject基类的GLib库中所做的那样。但这会遇到与使用void*指针类似的问题,因为您仍然需要依靠可能不安全的手动转换来进行上投和下投。

所以,是的,C使得编写通用且安全/易于维护的代码变得很困难,不幸的是,这可能导致代码重复。大型C项目经常在构建过程中使用脚本语言来生成重复代码。


1
人们还可以使用外部模板语言/工具或Shell脚本或使用ant或...生成重复的C代码
Job

您在答案中输入的这个Google混淆的俄语URL是什么?单击它会在我的浏览器中显示一个“打开”提示。这样安全吗?
罗伯特·哈维

2
@RobertHarvey我的防病毒和安全扫描认为很好。这是一个C语言头文件,扩展名为.h。看来合法。
maple_shaft

@maple_shaft:好的。我已经删除了链接周围的Googlization。
罗伯特·哈维

这只是纯文本.h(C头)文件
Charles Salvia 2013年

15

我不能为别人说话,但是以我自己在C方面的个人经验,代码重复并不是一个大问题。无论是由于项目规模还是迷人的样本集,我都不能说。但是,我认为有三个我认为适用的经验法则。排名不分先后...

  1. 为需要写东西。如果需要,一般性可以在以后出现。
  2. 如果需要通用性,则空指针,函数指针和结构大小可能是无价的。例如,例程qsort()使用了这三个函数。
  3. 重视使代码清晰。

5

值得注意的是,您可能不需要完全通用的解决方案来减少代码重复。有时,普通重构和一些方法概括就足够了。

在为广大受众编写通用解决方案时,您必须考虑:

  1. 消费者可以使用哪些新颖的方式使用您的代码,以及如何适应它们?
  2. 我需要捕捉哪些错误?什么错误,我应该没有赶上?
  3. API需要具有多强的功能?我需要提供多少个过载?
  4. 我必须采取哪些安全措施,以使代码不能用于邪恶目的?

埃里克·利珀特 Eric Lippert)撰写了整篇有关.NET框架代码中的单个设计决策的文章。总而言之,重构通常更简单。


3

我通常使用通用实现,例如glib的实现,然后如果转换最终变得太烦人了,请为其编写一个小型的特定于类型的包装器。但是,在C中期望有大量的转换,就像使用a void*作为泛型类型一样,因此在另一种语言中被认为是“混乱”的只是典型的C。获得更多的语言经验。


2
“在另一种语言中被认为是'混乱'的只是典型的C语言。足够讽刺的是,当您获得更多使用该语言的经验时,它看起来会更加自然。” 哈哈哈哈!C一直是我最喜欢的语言之一,但这仍然很有趣!
GlenPeterson

0

可重用性问题不是C独有的。但是,这里有一些出色且可重用的HashMap实现,您可以以此为基础。Java已经使用了大约十年或两年了:http : //docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

所有Java集合(包括HashMap)都针对Java 8进行了重写,以摆脱迭代器的概念,而是要求您将一个函数传递给集合,以便它可以在内部进行迭代。这对于编写并发代码是一个巨大的胜利-集合可以在内部管理并发性,因此客户端不必这样做。也就是说,大多数程序员都不习惯传递函数指针。这很好地解释了HashMap的去向:http : //cr.openjdk.java.net/~briangoetz/lambda/collections-overview.html

Scala的语法可能很难将类似的东西转换成C,但这是一个示例集合,该示例已经按照Java 8集合的方式工作了-默认情况下是不可变的,提供各种转换方法,使用函数指针和其他方法很酷的东西,例如部分应用程序:http : //www.scala-lang.org/api/current/index.html#scala.collection.Map

如果您正在使用HashMaps和其他更高级的数据结构做大量工作,则可能需要查看支持它们的语言。

回到一般的代码复制的想法,您可以通过剪切和粘贴来真正快速地编写代码,但是它会产生可怕的代码。我会说这在一次性报告(例如一次性报告)中是可以接受的。有时在没有下游代码的情况下也可以,例如UI代码只构建了一个屏幕,而其他都不依赖,但实际上这只是一个非常灰色的区域。

我认为说任何时候发现自己在复制代码时都应该创建一个函数并不是太过分简化。仅在代码的一个副本中修复的错误,而不是在另一个代码中修复的错误,就足以构成功能。


1
-“我不认为说任何时候您发现自己在复制代码时都应该创建一个函数就太过分了” –在C语言中,这太过简化了。尝试在C中为整数,双精度和字符串创建Stack,而不必为每种数据类型复制push和pop函数。
mike30 2013年

@mike使用void指针不可能吗?
sakisk 2013年

@mike-好点。但是有一个相对的观点,就是您可以为每种数据类型实现Stack,而无需再次实现Stack。因此,目标不是numberOfImplementations = 1,而是更多像5。您可能可以使int实现适用于chars。自从我使用C以来已经很久了,我已经记不清了。
GlenPeterson

1
@faif。是的,但是使用void指针的需要使C变得很重要。与C ++中的替代方法相比,强制转换指针更慢且更精致。稍高复制代码行列的可能的选择名单上的编码中C.当
mike30

@ mike30是否有关于以下方面的证明/参考:“在C中投射指针比在C ++中找到替代指针要慢?”
sakisk 2013年

0

我想知道在编写通用数据结构和C语言时,代码重复是否必定会带来麻烦?

在C中,对我而言,这绝对是我在C和C ++之间反弹的人。与C ++相比,我每天肯定会在C中复制更多琐碎的事情,但是故意地,并且我不一定认为它是“邪恶的”,因为至少有一些实际好处-我认为考虑所有事情是错误的严格来说是“好”还是“坏”-几乎所有事情都是权衡的问题。清楚地了解这些权衡是避免事后做出令人后悔的决定的关键,仅将事物标记为“好”或“邪恶”通常会忽略所有这些微妙之处。

正如其他人指出的那样,尽管问题并非C独有,但由于C中缺少比宏更宏,泛型指针无效的东西,非平凡的OOP的笨拙,C可能使问题更加恶化。 C标准库没有任何容器。在C ++中,实现自己的链表的人可能会激怒一群人,要求他们为什么不使用标准库,除非他们是学生。在C语言中,如果您不能自信地在睡眠中推出优雅的链表实现,那么您会招来愤怒的暴民,因为通常希望C程序员至少能够每天执行此类操作。它' 并不是由于对链接列表的某些奇怪的痴迷,Linus Torvalds使用SLL搜索和删除的实现,并使用双重间接作为标准来评估理解该语言并具有“良好品味”的程序员。这是因为C程序员可能需要在他们的职业生涯中实现一千多次这样的逻辑。在这种情况下,对于C来说,这就像厨师通过让新厨师准备一些鸡蛋来评估他们的技能来评估他们的技能,以了解他们是否至少掌握了所有需要做的基本事情。

例如,对于使用此分配策略的每个站点,我可能已经在C中本地实现了这种基本的“索引自由列表”数据结构十几遍(几乎我所有的链接结构都避免了一次分配一个节点并将内存减半) 64位链接的费用):

在此处输入图片说明

但是在C语言中,realloc当实现一个使用该数组的新数据结构时,只需将很少的代码存储到一个可增长的数组中,并使用索引方法将这些存储池中的一些内存用于空闲列表。

现在,我用C ++实现了同样的事情,而在这里我只将它作为类模板实现了一次。但这是C ++方面非常复杂的实现,包含数百行代码以及一些外部依赖关系,这些依赖关系也跨越数百行代码。而且它更复杂的主要原因是因为我不得不针对T可能是任何可能的数据类型的想法进行编码。它可以在任何给定的时间抛出(销毁它时除外,我必须像使用标准库容器一样显式地进行销毁),我必须考虑适当的对齐方式才能为T (尽管幸运的是,这在C ++ 11起变得更加容易),它可能是不可平凡的可构造/可破坏的(要求放置新的和手动的dtor调用),我必须添加一些方法,并不是所有东西都需要,但有些东西需要,而且我必须添加迭代器,包括可变迭代器和只读(常量)迭代器,依此类推。

可增长的阵列不是火箭科学

在C ++中,人们听起来像是std::vector火箭科学家的工作,经过优化以致于死亡,但它的性能没有比针对特定数据类型编码的动态C数组更好,后者仅用于realloc通过使用十几行代码。所不同的是,要使可增长的随机访问序列完全符合标准需要非常复杂的实现,避免在未插入元素上调用ctor(异常安全),提供const和非const随机访问迭代器,使用类型对于某些整数类型的特征,可以将范围控制器的填充控制器消除歧义T,可能会使用类型特征等对POD进行不同处理。等等。实际上,您确实需要一个非常复杂的实现来仅使可增长的动态数组成为可能,但这仅是因为它试图处理所有可能的用例。从好的方面来说,如果您确实需要存储POD和非平凡的UDT,并且可以使用可用于任何兼容数据结构的基于通用迭代器的算法,则可以从所有额外的工作中受益匪浅,从异常处理和RAII中受益,至少有时std::allocator使用您自己的自定义分配器进行覆盖等。当您考虑多少收益时,它肯定会在标准库中获得回报std::vector 在使用它的人们的整个世界上都有过,但这是针对旨在满足整个世界需求的标准库中实现的。

处理非常具体的用例的更简单的实现

由于仅使用“索引的免费列表”处理了非常具体的用例,尽管在C端实现了12次此免费列表,并因此重复了一些琐碎的代码,但我编写的代码可能更少了在C中实现该功能的总次数比在C ++中实现一次的总次数要多,而维护这十几个C实现的时间却比维护一个C ++实现的时间少。C端如此简单的主要原因之一是,每当我使用此技术时,我通常都会使用C中的POD进行操作,而我通常不需要的功能比inserterase在我在本地实施此功能的特定站点上。基本上,我可以只实现C ++版本提供的功能中最细小的子集,因为当我针对特定用途实现设计时,我可以自由地对设计的用途做更多的假设案件。

现在,C ++版本更加好用,而且类型安全,但是仍然是实现和使异常安全和双向迭代器兼容的主要PITA,例如,以一种可能会重复使用的通用实现方式在这种情况下,比实际节省的时间更多。而且,以普遍的方式实施它的大量成本不仅浪费在前期,而且以诸如每天不断增加的构建时间不断增加的形式反复浪费。

不是对C ++的攻击!

但这并不是对C ++的攻击,因为我喜欢C ++,但是在数据结构方面,我主要偏向于C ++,主要是因为我想花费很多额外的时间来预先实现这些真正不平凡的数据结构。这是一种非常通用的方法,可以针对所有可能类型的异常安全T,使符合标准的标准和可迭代性等等,在这种情况下,这种类型的前期成本确实可以以数英里的里程数回报。

但这也促进了非常不同的设计思想。在C ++中,如果我想为碰撞检测创建一个Octree,则倾向于将其推广到n级。我不只是要使其存储索引的三角形网格。当我唾手可得的超级强大的代码生成机制消除了运行时的所有抽象代价时,为什么要将它限制为只能使用一种数据类型呢?我希望它存储过程球体,多维数据集,体素,NURB表面,点云等,并尝试使其适用于所有事物,因为当您唾手可得的模板时,很想以这种方式进行设计。我什至不想将其限制为碰撞检测-光线跟踪,拾取等如何?C ++使其最初看起来“很简单” 将数据结构推广到第n级。这就是我过去在C ++中设计此类空间索引的方式。我试图设计它们以解决整个世界的饥饿需求,而我得到的通常是“万事通”,其代码极其复杂,以使其与可想到的所有可能用例保持平衡。

有趣的是,这些年来,我已经在C中实现的空间索引中得到了更多的重用,而且C ++完全没有错,而只是挖掘了该语言诱使我去做的事情。当我用C编写八叉树之类的代码时,我倾向于使它与点一起工作并对此感到满意,因为这种语言甚至很难将其推广到n级。但是由于这些趋势,多年来,我倾向于设计一些实际上更有效,更可靠的方法,并且非常适合手头的某些任务,因为它们不必担心通用性。他们成为一个专业类别的王牌,而不是所有行业的王牌。同样,这并不是C ++的错,而只是我在使用它而不是C时的人性化倾向。

但是无论如何,我喜欢两种语言,但是有不同的倾向。在CI中,有一种趋势,即不能一概而论。在C ++中,我倾向于概括太多。同时使用这两种方法可以帮助我平衡自己。

通用实现是规范,还是针对每个用例编写不同的实现?

对于一些琐碎的事情,例如使用数组中的节点或重新分配自身的数组(std::vector在C ++中与analog等效)的单链接32位索引列表,或者说是仅存储点并旨在不做任何事情的八叉树,我不会不必再泛泛到存储任何数据类型的地步。我实现了这些功能以存储特定的数据类型(尽管它可能是抽象的,并且在某些情况下使用函数指针,但至少比具有静态多态性的鸭子类型更具体)。

我对这些情况下的一点冗余感到非常满意,但前提是我要对其进行彻底的单元测试。如果我不进行单元测试,那么冗余就会变得更加不舒服,因为您可能拥有冗余代码,这些重复代码可能会重复出现错误,例如,即使您编写的代码类型不太可能需要进行设计更改,它可能仍然需要更改,因为它已损坏。我倾向于为我编写的C代码编写更全面的单元测试。

对于非同寻常的事情,通常是在接触C ++时,但是如果我要在C中实现它,我会考虑只使用void*指针,也许接受一个类型大小来知道为每个元素分配多少内存,以及可能使用copy/destroy函数指针深度复制和销毁数据,如果它们不是不可构造/不可破坏的。大多数时候,我不会打扰也不用太多的C语言来创建最复杂的数据结构和算法。

如果您经常使用一种数据结构处理特定的数据类型,则还可以将一种类型安全的版本包装在仅与位和字节以及函数指针一起使用的版本上void*,例如,通过C包装器重新施加类型安全性。

例如,我可以尝试为哈希映射编写通用实现,但是我总是发现最终结果很混乱。我还可以针对该特定用例编写专门的实现,使代码清晰易读,易于调试。后者当然会导致某些代码重复。

哈希表有点不稳定,因为如果需要自动隐式地增长表或可以预期表的大小,则哈希表的实现可能很琐碎,也可能非常复杂,具体取决于您对哈希,哈希表的需求的复杂程度。高级,无论您使用开放式寻址还是单独的链接等。但是要记住的一件事是,如果您完全根据特定站点的需求量身定制了一个哈希表,则它的实现通常不会那么复杂,并且通常会赢得如果为这些需求量身定做,它就不会那么多余。如果我在本地实现某些功能,至少这是我给自己的借口。如果不是,您可能只使用上述方法void*和函数指针来复制/销毁事物并将其概括化。

如果您的替代方案非常狭窄地适用于您的确切用例,那么通常不需要很费力气或很多代码就能击败非常通用的数据结构。举个例子,malloc用代码一次又一次地击败每个节点的使用性能绝对是微不足道的(而不是为多个节点池大量内存)即使malloc出现了更新的实现。击败它可能要花费一辈子的时间,并且编写同样复杂的代码,如果要匹配它的通用性,您必须将一生的大部分时间用于维护和更新它。

再举一个例子,我经常发现实现比Pixar或Dreamworks提供的VFX解决方案快10倍以上的解决方案非常容易。我可以在睡觉的时候做。但这不是因为我的实现出色-远非如此。对于大多数人来说,他们是完全卑鄙的。它们仅对我非常非常特定的用例而言是优越的。我的版本远不及Pixar或Dreamwork的普遍适用。这是一个荒谬的不公平的比较,因为与我的简单解决方案相比,他们的解决方案绝对出色,但这就是重点。比较不一定是公平的。如果您只需要一些非常具体的内容,则无需使数据结构处理无数无用的列表。

同质位和字节

在C语言中,由于它本质上缺乏类型安全性,因此需要加以利用的一件事是,根据位和字节的特性来均匀地存储事物。内存分配器和数据结构之间还有更多的模糊。

但是存储一堆可变大小的东西,甚至事情,只是可能是大小可变的,就像一个多态DogCat,难以有效地做到。您不能假设它们可以可变大小并将它们连续存储在一个简单的随机访问容器中,因为从一个元素到另一个元素的步幅可能会有所不同。结果是要存储一个同时包含狗和猫的列表,您可能必须使用3个单独的数据结构/分配器实例(一个用于狗,一个用于猫,一个用于基本指针或智能指针的多态列表,或更糟糕的是,将每个狗和猫分配给通用分配器,然后将它们分散到整个内存中),这会变得昂贵,并招致高速缓存未命中的份额。

因此,尽管降低了类型丰富性和安全性,但在C中使用的一种策略是在位和字节级别上进行概括。您可能可以假定DogsCats要求使用相同数量的位和字节,具有相同的字段以及指向函数指针表的相同指针。但是,作为交换,您可以减少对数据结构的编码,但同样重要的是,可以有效且连续地存储所有这些内容。在这种情况下,您像对待类比工会一样对待狗和猫(或者您可能实际上只是使用了工会)。

确实要付出巨大的代价来键入安全性。如果在C中我想念的比什么都重要,那就是类型安全。它越来越接近汇编级别,在该级别结构仅指示分配了多少内存以及如何对齐每个数据字段。但这实际上是我使用C的第一原因。如果您确实要控制内存布局以及所有内容的分配位置以及彼此之间相对存储的位置,那么通常只需要按位和级别考虑即可。字节,以及解决特定问题所需的位数。在那里,C的类型系统的笨拙实际上可以变得有益而不是障碍。通常情况下,最终导致要处理的数据类型要少得多,

虚假/表观重复

现在,我一直在宽松地使用“复制”来处理甚至可能不是多余的事情。我已经看到人们将“偶然/表观”重复与“实际重复”区别开来。我的看法是,在许多情况下没有如此明显的区别。我发现这种区别更像是“潜在唯一性”与“潜在重复性”之间的区别,无论哪种方式都可以。它通常取决于您希望设计和实现如何发展,以及它们将如何针对特定用例进行完美定制。但是我经常发现,在经过多次改进之后,后来看起来似乎是代码重复的东西不再是多余的。

使用realloc与的类比等效的简单可增长数组实现std::vector<int>。最初,例如std::vector<int>在C ++中使用它可能是多余的。但是您可能会发现,通过测量,预先分配64个字节以允许插入16个32位整数而不需要进行堆分配可能是有益的。现在,它不再是多余的,至少不是std::vector<int>。然后您可能会说:“但是我可以将其概括为new SmallVector<int, 16>,并且可以。但是然后我们说您发现它很有用,因为它们是用于非常短的,短寿命的数组,使堆分配的数组容量增加四倍,而不是增加1.5(大约是vector实现时使用),同时假设阵列容量始终是2的幂。现在,您的容器确实有所不同,并且可能没有像它这样的容器。也许您可以尝试通过添加越来越多的模板参数来自定义预分配繁重,自定义重新分配行为等来概括此类行为,但是到那时,与十几行简单的C语言相比,您可能会发现确实难以使用码。

甚至可能达到需要分配256位对齐和填充内存的数据结构,专门存储AVX 256条指令的POD,预先分配128字节以避免普通情况下小输入大小的堆分配,容量翻倍的数据结构的程度。已满,并允许安全覆盖超过数组大小但不超过数组容量的尾随元素。在这一点上,如果您仍在尝试推广一种解决方案,以避免重复少量的C代码,那么编程之神可能会对您的灵魂充满怜悯。

因此,在类似的情况下,当您开始量身定制解决方案以使其越来越好并更好地适合特定用例时,这种情况最初看上去就开始变得多余,而冗余完全消失了。但这仅适用于您有能力针对特定用例完美定制它们的事情。有时,我们只需要针对我们的目的将其概括的“体面”的东西,在那儿,我将从非常通用的数据结构中受益最大。但是,对于针对特定用例完美地完成的特殊事情,“通用”和“针对我的目的完美”的想法开始变得太不兼容了。

POD和基元

现在,在C语言中,我经常会找到借口将POD(尤其是图元)存储到数据结构中。这似乎是一种反模式,但实际上我发现它无意中有助于提高代码的可维护性,而这些能力是我过去在C ++中经常要做的事情的类型。

一个简单的例子是插入短字符串(这通常是用于搜索关键字的字符串的情况-它们往往很短)。为什么还要处理所有这些长度可变的字符串,这些字符串在运行时会发生变化,这意味着不平凡的构造和破坏(因为我们可能需要堆分配和释放)?如何将这些内容存储在中央数据结构中,例如仅用于字符串实习的线程安全的trie或哈希表,然后使用普通旧字符int32_t或引用这些字符串:

struct IternedString 
{
    int32_t index;
};

...在我们的哈希表,红黑树,跳过列表等中,如果我们不需要字典排序?现在,我们编码为与32位整数一起使用的所有其他数据结构现在都可以存储这些有效的只是32位的字符串键ints。而且我至少在用例中发现了(由于我从事射线跟踪,网格处理,图像处理,粒子系统,绑定到脚本语言,低级多线程GUI套件实现等领域,因此可能只是我的领域了-底层代码,但不像OS那样底层代码),代码恰好存储这样的索引恰好变得更加高效和简单。因此,我经常在75%的时间里以公正int32_tfloat32 在我不平凡的数据结构中,或者只是存储相同大小的东西(几乎总是32位)。

当然,如果这适用于您的情况,则可以避免针对不同数据类型使用多种数据结构实现,因为一开始您将只使用很少的数据结构。

测试与可靠性

我要提供的最后一件事可能并不适合所有人,那就是赞成为这些数据结构编写测试。让他们真正擅长某事。确保它们非常可靠。

在这些情况下,一些次要的代码重复变得可以原谅,因为如果您必须对重复的代码进行级联更改,那么代码重复只是维护负担。通过确保冗余代码超可靠并且确实非常适合其工作,您消除了更改此类冗余代码的主要原因之一。

这些年来,我的审美观念发生了变化。我不再烦恼,因为我看到一个库实现了点积或已经在另一个库中实现的一些琐碎的SLL逻辑。我只会在事情测试不佳且不可靠时感到恼火,而且我发现这种思维方式更具生产力。我真正处理过的代码库是通过重复的代码来复制错误,并且看到了复制和粘贴编码的最坏情况,使原本应该微不足道的更改变成一个集中的地方,却变成了容易出错的级联更改。然而,在许多情况下,这是由于测试失败,代码未能变得可靠和擅长于其最初所做的事情而导致的。在我使用有问题的旧代码库之前,我的想法与所有形式的代码复制相关,因为它们很可能复制错误并需要级联更改。然而,即使在某个地方或那里有一些看起来很冗余的代码,微型图书馆如果做得非常好并且非常可靠,那么将来几乎不会发现需要更改的原因。当时我的工作重点没有了,那时候重复让我感到烦恼的不仅仅是质量差和缺乏测试。后面这些事情应该是头等大事。

极简代码复制?

这是一个很有趣的想法,突然出现在我的脑海中,但考虑到一种情况,我们可能会遇到一个C和C ++库,它们大致具有相同的作用:两者具有大致相同的功能,相同的错误处理量,但并不是很明显而且效率更高。最重要的是,两者均能胜任地实施,经过良好的测试和可靠。不幸的是,在这里我不得不假设地说,因为我还没有发现任何接近完美的并排比较的东西。但是,我所发现的与这种并排比较最接近的东西通常是C库比C ++等效库小得多(有时是其代码大小的1/10)。

而且我相信这样做的原因是,再次以通用方式解决问题,即处理最广泛的用例而不是一个确切的用例,可能需要数百到数千行代码,而后者可能只需要一打。尽管存在冗余,并且尽管C标准库在提供标准数据结构方面非常糟糕,但它最终最终却减少了人工编写代码来解决相同的问题,我认为这主要是由于两种语言在人类倾向上的差异。一种促进针对非常特定的用例解决问题,另一种倾向于针对最广泛的用例促进更抽象和通用的解决方案,但这些最终结果并没有

前几天,我在github上看某人的raytracer,它是用C ++实现的,所以需要这么多的玩具raytracer代码。而且我没有花太多时间在看代码,但是那里有大量的通用结构可以处理,远远超出了raytracer的需求。而且我认识到这种编码风格,因为我过去曾经以一种超级自下而上的方式使用C ++,重点是首先制作一个非常通用的数据结构的成熟库,该库首先要超越即时数据。解决眼前的问题,然后再解决实际问题。但是,尽管这些通用结构可能会在这里和那里消除一些冗余,并在新的上下文中享有很多重用性,作为交换,它们通过交换一些冗余与大量不必要的代码/功能来极大地膨胀项目,而后者不一定比前者容易维护。相反,我经常发现很难维护,因为很难维护通用设计,而通用设计必须严格平衡设计决策与可能的最广泛需求。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.