weixin_39559804
2020-12-01 16:57 阅读 2

关于代码的一些问题

有幸在B站上看见了UP的视频,很感兴趣,简单地看了一下repo。 本身也是C语言开发者,目前是在做OS相关的学习和工作。因为很喜欢也经常使用C语言,所以对于代码中的很多问题不吐不快,还望UP见谅。

首先,想说的是,UP这样简单地根据代码行数(或字符数)的多少来评定代码是否精简的做法是完全错误的。并不是说想办法精简自己的代码,想方设法地把代码变得更简洁是错误的。而是说,代码是否精简与行数/字符数是没有必然联系的。因为编译器其实根本不在乎这个问题。编译器不会在乎代码是通过多少行完成的,只要是使用同样逻辑的代码,不管UP怎么消减行数,最终所编译出来可执行文件几乎都是一样的(可能根据编译器的优化策略会有细小差距)。这样就更不存在什么代码精简与否的问题了。

更糟糕的是,这样的代码会带来诸多问题。代码的可读性首当其冲,在这样的代码压缩下,不仅其他读者看不懂代码,UP自己过几个月之后再来阅读代码也会毫无疑问会很费劲。但可读性的问题都还不是最严重的。为了压缩代码而在代码中使用的各种各样的所谓“骚操作”使得代码的健壮性根本无从谈起。

不妨结合实际的代码来做一个分析,我这里选用的是GluttonousSnake/1_tiny_14lines.c

C
1   #include <stdlib.h>
2   #include <conio.h>
3   int main() {
4       int W = 20, S = W * W, *m, z[2] = {0}, l = 3, i, c = 'D', C, *p, f;
5       for (srand(m = calloc(S, 4)), C = m[1] = -1; C - 27; _sleep(100)) {
6           if (_kbhit())C = _getch() & 95, C - 65 && C - 68 &&
7               C - 83 && C - 87 || (C ^ c) & 20 ^ 4 && (c = C);
8           p = z + !!(c & 2), *p += c / 3 & 2, *p = (--*p + W) % W;
9           f = !system("cls"), *(p = m + *z + z[1] * W) > 0 && (C = 27);
10          for (; *p && (m[i = rand() % S] || (--m[i], ++l, --f)););
11          for (i = 0, *p = l; i < S; ++i % W || _cputs("|\n"))
12              _cputs(m[i] > 0 ? m[i] -= f, "()" : m[i] ? "00" : "  ");
13    }
14 }
</conio.h></stdlib.h>
  1. 在同一行声明多个不同类型、不同含义的变量,且变量的命名不规范(Line 4) 通常来说,无论是局部变量也好,还是全局变量也好,都最好是根据变量的含义分多行进行声明和初始化。这样做的好处是能够增加代码的可读性。考虑下面这段代码:
C
int cnt, sum, i, arr[5];

诚然,这样的代码也是能容易读懂的,但如果我们改成下面这样:

C
int cnt;     // counter for the iteration 
int sum;   // total sum of input 
int i;         // iteration var 
int arr[5];

通过分行声明,我们能够在变量后面加入适当的注释,使得他人能够更容易地读懂我们的代码。 另一个方面,对变量使用恰当/规范的命名也能增强代码的可读性与可维护性(通常来说,拥有良好的可读性往往意味着良好的可维护性)。在C语言以及大多数的编程语言中,惯用的命名法有蛇形命名法和驼峰命名法。UP感兴趣的话可以去了解一下。btw,全大写的变量名(更为严谨的说法应该是Identifier,标识符)往往代表常量,变量是不不应该使用全大写的。

  1. 对于calloc这样的内存管理函数的错误运用(Line 5) 内存管理是C语言中的问题高发区,同时也是开销大户(从OS的层面来说callocmalloc这样的函数属于syscall的范畴,会需要调用对应的syscall handler函数,带来大量的时间开销)。那么考虑一个问题,在什么情况下,我们应该使用这样的内存分配函数?答案是当我们需要一段无法确定长度的内存空间时,即当我们需要动态地使用内存空间时。而显然这里并不属于这种情况。如果只是要求一段定长的数组,简单地使用数组就可以了。还有一个问题的代码中缺少对应的free语句来释放内存。诚然在进程(Process)结束时OS会释放掉这些内存,但是养成手动释放内存的习惯将能够更好地避免内存泄漏问题(这在程序的规模变大的时候会显得很致命)。

  2. 滥用逗号运算符 代码中不少的连续运算操作都使用了,用于连接两个连续的表达式。尽管因为,运算符有着最低的优先级而使得大多数情况下这样的写法是没有问题的。但是,为什么不直接使用;呢?希望的功能是完全一样的,而且也绝对不会出现问题。

  3. 慎用可能引起副作用的操作符(自增、自减等操作) 这些操作通常会带来副作用,所以尽量不要在比较长且复杂的表达式中使用这些操作符。

  4. 缺失的return 0; 对于现代的大多数编译器来说这并不是什么问题,有些编译器会在编译时自动补全这样的缺省,但我们并不能保证也不应该指望编译器总会这样做。但从更好的代码风格的角度考虑,尤其是main函数实际上是要求返回一个int型变量的,最好是不要落下这句return 0;。UP感兴趣的话也可以去查一查main()函数返回值到底意味着什么。

代码的问题其实很多,限于篇幅先只写这么多。说实话,并不是想打击UP学习编程的积极性。而是我相信一些建议能够帮助UP写出更好的代码。在写代码时,绝不是说代码越简短越好。代码是给人看的,代码的可读性才应该是程序员首要考虑的东西之一(当然性能也很重要,但好的性能往往由算法这样的更上层的因素或是ABI这样的底层因素决定,而不是代码的长短),在实际的工作中有时甚至会牺牲些许性能来保证代码的可读性。

当然,我很认同UP尽可能精简自己代码的想法与努力。但我想,这个方向可能不是正确的。如果UP真的希望能够提高自己代码的性能并精简整个代码逻辑的话,不妨试试从算法/数据结构的角度着手。我个人比较推荐的是Mark Allen Weiss所著的《数据结构与算法分析(C语言描述)》(Data Structures and Algorithm Analysis in C);当然,如果UP尚有余力的话,可以去看看富有盛名的CSAPP(Computer Systems: A Programmer's Perspective),这些书会帮助你更好地理解“编程”这一行为,并写出更好的代码。

最后送UP一句话,也是我很喜欢的一句话:“真理总是美的,而美总是简单的。”在此谨祝UP学业顺利。

该提问来源于开源项目:RainbowRoad1/Cgame

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享

5条回答 默认 最新

  • weixin_39621695 weixin_39621695 2020-12-01 16:57

    回复地有点晚, 抱歉... 根据你的每一条建议来回复...

    '用行数来判定代码精简' 多少还是有问题的, 这不可否认. 但如果是建立在一定规则下的话, 还是能够作为一个参考指标的吧, 多少还是能和代码量成正比, 如果你有尝试过格式化这些代码, 你会发现代码基本没有任何变化(vs2019没有变化, vscode多3行 与换行相关) 我也明白, up对代码精简的标准并不能受到多数人的认可, 即使如此, 也还是有很多人乐意去研究这些代码...

    可读性这个问题, 第一次看这种代码肯定是看不懂的了, 但为了达到压缩极限, 这不可避免...当然可读性的标准都是主观上的, 那就假设和其他代码做个比较吧: 一个100行贪吃蛇一个14行贪吃蛇, 你会看哪个? 被这种极短代码震撼到了, 即使看不懂, 相信你也会愿意去研究这种代码吧, 何况还有视频动画讲解... up对这种代码习以为常了...就算过去几个月, 别说阅读代码, 手撕代码都没问题...每个代码都重写过无数遍, 都让我印象深刻, 我认识的某个大佬, 研究我这些代码, 也能达到手撕代码的地步了(这大佬平时都很谦虚, 应该不会吹...)

    后面都跟我的代码风格有关了, 都是小问题...

    1. 变量就几个, 能懂就行, 注释都写动画里了, 2.1k行注释(bushi)
    2. 就用一次calloc()也不至于这么夸张吧...直接定义数组的话, 下标不能为变量比较麻烦(宏定义不考虑, 那还得多一行), 或许你会说不定长数组, vs不支持这特性就没考虑了...而且不定长数组不能初始化, 需要遍历一次来赋值, 何必呢?
    3. 逗号运算符其实还好吧, 都遵循规范了, 换成;就不行了, 格式化会换行的
    4. ???
    5. ......

    问题确实挺多的, 多说点也没关系的, 这并不是打击up, 是对up的肯定对吧? 不然也不会在这评论了...简短的代码也仅限于这些小游戏了, 正常的项目还是会正常写的...

    点赞 评论 复制链接分享
  • weixin_39647499 weixin_39647499 2020-12-01 16:57

    嗯UP你是不是回错人了??我好像没有提过这些建议……(雾

    但是看到(可能是别人)对UP的建议,我就这几个方面也谈谈我的看法吧(笑

    首先就是可读性,我认为这个项目的意义本身就不在可读性(笑)。因为我是一名OIer(信息学竞赛选手),所以很理解UP用的一些非常巧妙的处理方法。而对于一个代码的阅读者,我认为去尝试理解UP写出这些处理时的思路是非常有趣的,也是这个项目真正的,我觉得的最有意义的地方。

    其次是娱乐性,因为小游戏写的人很多,所以本身不期待在娱乐性上有太大的亮点。(这不是对UP的否定啊(笑 但是我要承认,在看到那几个用窗口表现的贪吃蛇,2048与俄罗斯方块之类的游戏时,还是有被震撼到的。我之前也有过类似的想法,但是一直咕着(笑),UP已经很厉害了!

    此外,我是一名C++er,因为UP的一些特殊写法,导致g++不能直接编译过。所以我现在在做的一件事就是把UP的C写成C++,还是很有趣的(笑

    最后,我去加了UP您的群,但是不知道为什么加进去就被踢出来了……希望您重新加我一下(笑)(QQ号就是这个邮箱号)

    永远支持UP!

    ------------------ 原始邮件 ------------------ 发件人: "RainbowRoad1/Cgame" <notifications.com>; 发送时间: 2020年10月24日(星期六) 中午11:11 收件人: "RainbowRoad1/Cgame"<Cgame.github.com>; 抄送: "Subscribed"<subscribed.github.com>; 主题: Re: [RainbowRoad1/Cgame] 关于代码的一些问题 (#4)

    回复地有点晚, 抱歉... 根据你的每一条建议来回复...

    '用行数来判定代码精简' 多少还是有问题的, 这不可否认. 但如果是建立在一定规则下的话, 还是能够作为一个参考指标的吧, 多少还是能和代码量成正比, 如果你有尝试过格式化这些代码, 你会发现代码基本没有任何变化(vs2019没有变化, vscode多3行 与换行相关) 我也明白, up对代码精简的标准并不能受到多数人的认可, 即使如此, 也还是有很多人乐意去研究这些代码...

    可读性这个问题, 第一次看这种代码肯定是看不懂的了, 但为了达到压缩极限, 这不可避免...当然可读性的标准都是主观上的, 那就假设和其他代码做个比较吧: 一个100行贪吃蛇一个14行贪吃蛇, 你会看哪个? 被这种极短代码震撼到了, 即使看不懂, 相信你也会愿意去研究这种代码吧, 何况还有视频动画讲解... up对这种代码习以为常了...就算过去几个月, 别说阅读代码, 手撕代码都没问题...每个代码都重写过无数遍, 都让我印象深刻, 我认识的某个大佬, 研究我这些代码, 也能达到手撕代码的地步了(这大佬平时都很谦虚, 应该不会吹...)

    后面都跟我的代码风格有关了, 都是小问题...

    变量就几个, 能懂就行, 注释都写动画里了, 2.1k行注释(bushi)

    就用一次calloc()也不至于这么夸张吧...直接定义数组的话, 下标不能为变量比较麻烦(宏定义不考虑, 那还得多一行), 或许你会说不定长数组, vs不支持这特性就没考虑了...而且不定长数组不能初始化, 需要遍历一次来赋值, 何必呢?

    逗号运算符其实还好吧, 都遵循规范了, 换成;就不行了, 格式化会换行的

    ???

    ......

    问题确实挺多的, 多说点也没关系的, 这并不是打击up, 是对up的肯定对吧? 不然也不会在这评论了...简短的代码也仅限于这些小游戏了, 正常的项目还是会正常写的...

    — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

    点赞 评论 复制链接分享
  • weixin_39621695 weixin_39621695 2020-12-01 16:57

    没回错啊... 如果你有关注这个项目(右上角的Watch), 大概都会收到邮件...刚刚看了一下, 你确实在列表里...

    g++编译器不能通过编译的原因主要是类型转换, 因为c++对类型检查比较严格?

    加进去被踢出来的原因是你入群问题答错了...重新加群回答就行

    点赞 评论 复制链接分享
  • weixin_39647499 weixin_39647499 2020-12-01 16:57

    啊好的,谢谢UP!

    (就是UP这封邮件的口吻有点像给某一个人(笑

    ------------------ 原始邮件 ------------------ 发件人: "RainbowRoad1/Cgame" <notifications.com>; 发送时间: 2020年10月24日(星期六) 中午12:05 收件人: "RainbowRoad1/Cgame"<Cgame.github.com>; 抄送: "Walker-V"<1841874686.com>;"Mention"<mention.github.com>; 主题: Re: [RainbowRoad1/Cgame] 关于代码的一些问题 (#4)

    没回错啊... 如果你有关注这个项目(右上角的Watch), 大概都会收到邮件...刚刚看了一下, 你确实在列表里...

    g++编译器不能通过编译的原因主要是类型转换, 因为c++对类型检查比较严格?

    加进去被踢出来的原因是你入群问题答错了...重新加群回答就行

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

    点赞 评论 复制链接分享
  • weixin_39624332 weixin_39624332 2020-12-01 16:57

    看不懂

    点赞 评论 复制链接分享

相关推荐