是否有关于如何编写现代C的公认准则?


13

我有很强的Java / Groovy背景,并且已被分配给一个为管理软件维护大量C代码库的团队。

Java Web服务已经外部化了一些痛点,例如处理数据库中的Blob或生成PDF和Excel中的报告。

但是,作为Java开发人员,我对代码的某些方面感到有些困惑:

  • 它很冗长(尤其是在处理“异常”时)
  • 有很多巨大的方法(很多2000行以上的方法)
  • 没有高级数据结构(我很想念List,Set和Map)
  • 无需关注(SQL在代码中充满欢乐)

结果,我觉得企业隐藏在大量的技术代码中,而我的大脑(由面向对象和少量的功能编程所塑造)并不轻松。

该项目的好处是代码简单明了:没有框架,没有运行时的字节码操作,也没有AOP。并且服务器可以通过使用比Java吐出“ hello world”所需的内存更少的内存,在一台计算机上同时回答10000多名用户。

我想学习如何根据公认的现代原理编写C代码。关于如何编写和构造现代C,是否有任何公认的原则?

有点像“有效Java”书的等效内容,但适用于C。

根据答案和评论进行编辑:

  • 我将尝试使我的思维方式适应C代码,而不是尝试将其镜像到OOP。
  • 我已经开始从评论中扫描推荐的编码风格指南(GNU编码标准和Linux内核编码风格)。
  • 然后,我将尝试向我的同事提出这种代码样式。最困难的部分可能是说服同事,将巨大的方法拆分为较小的部分,并且借助一种方法可以避免重复相同的4行错误处理代码。

5
该应用程序实际上是否需要现代化,或者您只是认为它是因为它的编写方式不熟悉而已?
Blrfl


1
@Blrfl,我认为该应用程序是用过时的标准编写的。我只是想知道今天(行政)C标准(2016年)是什么。如果有的话。我不想重构或重塑当前应用程序,我想知道如何编写代码的下一部分。
Guillaume


3
@antlersoft:2,000行函数可以一长串地处理一系列简单的事情,这绝对没有问题,不需要借口。请不要使用诸如“您不应该编写2,000个行函数,因为您不应该编写2,000个行函数”之类的循环参数进行回复。
gnasher729

Answers:


14

我可以从您的问题中读到,问题不在于代码不是旧的C,而是不好的编程。您提到的大多数问题,例如冗长程度,庞大的2000+行功能或没有关注点分离都适用于任何语言,无论C还是Java。

在错误处理的上下文中提到了详细性。您没有提供示例,所以我只能提醒您,错误处理代码也是code。样板代码的重复部分没有任何借口。算出来;要么转到一个函数,要么(如果不值得创建一个单独的函数)执行该goto Error;模式并将错误处理和资源清理移至Error:该函数底部的某个部分。

如果将错误传递给调用链似乎是问题,请问自己:那里的函数真的需要知道这里的一些小家伙有问题吗?内置在语言中的异常机制使其更容易实现,但是通常最好尽早处理异常(使用任何语言),以使错误条件不会污染高级代码的逻辑。并且如果确实需要知道那里的函数,则可以使用和来模拟异常setjmplongjmp

我认为唯一提到的与C相关的问题是缺少标准容器。虽然Set通常可以将其替换为排序后的数组,并且Map(大部分情况下)可以替换为成对的数组或struct(如果您事先知道密钥集,map[key] = value则可以替换成s.key = value),但是事实是标准中没有动态数组容器图书馆。在C99中,您至少可以在堆栈(int array[len])上声明一个可变长度的数组,但是您需要len事先计算(通常不难),当然您不能将其作为任何堆栈分配的对象返回。大多数项目最终都会编写自己的动态数组容器或采用开源的容器。

最后,我想指出我去过那里。我曾经是使用C ++和纯C语言的Java程序员。我建议“阅读X来学习优秀的C语言”,但是没有什么比Java没有的了。前进的方法是吸收语言和标准库的所有复杂性。谷歌很多,阅读很多,很多代码,直到您开始使用C进行思考。尝试像用Java一样用C编写东西就像在外语中用从母亲那里直接翻译来的句子来编写一样令人沮丧舌; 您和读者都会畏缩。好消息是学习好的编程很慢,但是学习另一种语言很快。因此,如果您使用Java编写了体面的代码,


1
总而言之,这是一个很好的答案。我只是反对将setjmp()/ longjmp()视为有效工具:它甚至不尝试执行任何清理。任何分配都将被泄漏,任何持有的锁将不会被释放,任何打开的文件都不会被关闭,并且任何瞬时数据不一致都将成为永久性的。恕我直言,这个功能对基本上是有史以来最糟糕的黑客,唯一的理由就是可以实现它。最后,实际上只有一种有效的方法可以在C中进行错误处理:显式错误代码。
cmaster-恢复莫妮卡

@cmaster是的。就个人而言,setjmp/longjmp在C语言中似乎是一条没水的鱼,而我从未使用过它们。我之所以被迫把它们包括在内是因为互联网上有大量的模仿异常的教程/库,所以我认为确实有人在使用它。
一只猫头鹰

7

该项目的好处是代码简单明了:没有框架,没有运行时的字节码操作,也没有AOP。并且服务器可以通过使用比Java吐出“ hello world”所需的内存更少的内存,在一台计算机上同时回答10000多名用户。

我建议您谨慎一点,这是否值得您花时间,以及公司是否会花资源来“现代化”具有低代码复杂性且性能良好的工作软件。您很有可能会自己引入新的错误,尤其是因为它似乎是您不熟悉的系统。

如果您仍然想走那条路线,那么我建议以下几点:

  • 制作(或生成)软件/代码的状态图
  • 深入研究代码并分别列出代码中最复杂或最关键的部分
  • 找一个了解该代码库的人,然后问他们为什么要用这种方式构建它,以及已知会导致什么问题的原因
  • 根据所学内容编写文档

此时,您将决定是否值得探索。如果您公司的文化不能奖励失败,那么请高层或经理来开绿灯。

  • 对软件的不同构建块进行分区,并为每个编写单元测试。
  • 迭代直到您可以将不同的模块粘合在一起
  • 做进一步的测试以模拟真实的用户交互(压力测试等)

我认为这是一个相当不错的路线图,可以根据需要将您带到任何地方。不了解该项目的细节,很难为您提供很多帮助。请不要将我的免责声明视为过于危言耸听。通过尝试将现有项目重写为他们喜欢的语言或使用“现代”工具,成千上万的优秀程序员大跌眼镜。这是一个必须仔细考虑的决定,我敦促您不要流氓,在管理上的支持或同事的帮助下,自己去做。


2
我意识到我的问题根本不清楚。我不想重构代码。完全没有 我想按原样维护现有代码库。但是,我想学习如何为新功能编写现代C语言。在这里我迷路了。我发现的大多数文档都是关于如何用C语言编写代码,而不是有关如何编写“现代” C语言。也许没有像“现代” C语言这样的东西……
Guillaume

1

如果您希望使用高级语言,则可以很容易地将某些语言(如C ++或Objective-C)与C代码混合。

另外,C和C ++是合理兼容的。您可能只需做很少的更改就可以将整个代码库编译为C ++-您将需要偶尔重命名为“ class”或“ template”的变量,但实际上,仅此而已。(sizeof('a')在C和C ++中是不同的,但我认为我从未使用过)。

如果您走这条路,请考虑下一个维护者可能不太熟练使用C ++。不要被带走。利用C ++的优势,但是到目前为止,C程序员可以轻松理解它。


1
我必须在这里不同意。C和C ++是不同的语言,C ++编译器要求的某些代码(显式转换的返回值malloc)在C中被认为是不好的做法。C和C ++之间的const和含义inline非常不同,当然C ++无法理解__restrict。不要将语言视为可互换的,即使是在两种语言都可以编译的源子集中也是如此。
Angew不再为SO

1

基本上,编写良好的C代码与编写良好的C ++或Java代码相同:您需要一个类,请使用struct。您需要继承,将基础struct作为无名第一成员。要使用虚函数,请向静态struct的函数指针添加一个指针。依此类推,等等。这正是C ++在幕后所做的,唯一的区别是,它在C中是显式的。因此,您可以在C中完美地进行面向对象的编程,它看起来与您相比有一点点不同,而且更加简洁用于。

关键是,好的编程是关于范式的,而不是关于语言功能的。的确,如果您的语言功能为要使用的范例提供了良好的支持,那总是很好,但是语言功能不是必需的。一旦意识到这一点,就可以用几乎任何一种语言(除了诸如Brainfuck或INTERCAL等深奥的语言)编写好的代码。

当然,问题仍然在于标准C库不包含您惯用的任何那些漂亮的容器类。不幸的是,这意味着您将需要使用自己的,或者通过使用动态分配的数组来解决这种不足。但我敢打赌,您很快就会发现,您真正需要的是malloc()通过类中的指针成员实现的动态数组()和链接列表/树。

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.