我是否为了较长的“列”代码而牺牲了较短的变量名?


17

我是CS课的业余程序员,试图学习适当的编程技能。这就是我的代码的样子,它的边缘扩展到103列。

int extractMessage(char keyWord[25], char cipherText[17424],
                   int rowSize, char message[388]) 
{
    int keyColumn    = 0;
    int cipherColumn = 0;
    int offset       = 1;
    int nextWord     = 1;

    int lengthOfWord   = 0;
    int lengthOfCipher = 0;

    lengthOfWord = length(keyWord);
    lengthOfCipher = length(cipherText);


    while (keyWord[keyColumn] != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyWord[keyColumn + offset] != cipherText[cipherColumn + (rowSize*nextWord) + nextWord]) {
            cipherColumn++;
            continue;
        }
    }

之前,我有那些超级长变量名了,我想I,J,K的事,但我的教授坚持认为,我们不使用变量,例如,在“专业领域”等lenWord,即使缩短变量是不够的,因为人们可能会认为它代表“伦纳德的世界文学”。他说选择有意义的变量名,但是这样做我觉得自己违反了编码的黄金法则,将其限制在80列以下。我该如何解决?


26
继续; 添加更多有用的名称。你能想到的方式来描述cipherColumn + (rowSize*nextWord) + nextWord这清楚地表明,计算是什么,例如?我敢打赌,这个名字比计算的要短,因此您将获得可读性减少的行长。也不要对齐分配,否则,如果重命名最长的变量,则必须全部移动分配。
jonrsharpe

2
嗯..所以您是说我应该创建一个新变量并将cipherColumn +(rowSize * nextWord)+ nextWord的结果存储到其中,以便我可以进一步使用它?那是专业人士所做的吗?我真的是在问
RaulT

8
是的,这是我的建议。我是专业人士,这就是我会做的,所以……至少其中一些。
jonrsharpe

11
黄金法则是编写可以阅读和理解的代码。我们为其他人而不是为机器编写代码(!)。对于机器,有机器代码。对于某些人来说,看起来像您所描述的代码(单字母名称等)对其他程序员(以及将来的您)缺乏尊重,因为您将在接下来的几周或几个月内忘记这些代码。没有理由坚持使用80列,而是80年代的MS DOS。
rsm

3
@stijn是的,但这是我们最后一次需要它。就像我不为8位8086处理器编译我的C代码以防万一我需要将其存储在打孔卡上一样,我也不认为80列金色标准在21世纪具有任何意义。我们应该扩展这项技术,而不是坐在80年代,并认为它使我们成为聪明的黑客。聪明之处在于简单性,可读性并将技术推向最大。我们有全高清显示器,是时候使用它了。
rsm

Answers:


24

通常,当我看到像您这样发布在此处的代码时,我会对其进行编辑,因为我们讨厌水平滚动。但是,由于这是您的问题的一部分,因此我将在此处向您显示编辑内容:

int extractMessage(char keyWord[25], char cipherText[17424],
                   int rowSize, char message[388]) 
{
    int keyColumn    = 0;
    int cipherColumn = 0;
    int offset       = 1;
    int nextWord     = 1;

    int lengthOfWord   = 0;
    int lengthOfCipher = 0;

    lengthOfWord = length(keyWord);
    lengthOfCipher = length(cipherText);


    while (keyWord[keyColumn] != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyWord[keyColumn + offset] 
        != cipherText[cipherColumn + (rowSize*nextWord) + nextWord]) {
            cipherColumn++;
            continue;
        }
    }
}

这突破可能会令人吃惊,但它比与横向滚动的版本更具可读性,它是优于缩短名字ijk

这并不是说你不应该使用ijk。在索引3个嵌套for循环时,这些名字很不错。但是,这里的名字确实是我关于您期望发生的事情的唯一线索。特别是由于此代码实际上没有执行任何操作。

遵循变量名长度的最佳规则是范围。变量的生存时间越长,其名称就必须与其他变量竞争。名称CandiedOrange在堆栈交换中是唯一的。如果我们在聊天中,您可能会称我为“ Candy”。但是现在,您所处的名称可能会与CandideCandy ChiuCandyfloss混淆。因此,范围越长,名称应越长。范围越短,名称就越短。

线路长度应永远决定名称的长度。如果您觉得这样,那么可以找到另一种方式来布置代码。我们有许多工具可以帮助您做到这一点。

我要寻找的第一件事是消除不必要的噪音。不幸的是,这个例子没有做任何事情,所以都是不必要的噪音。我需要处理一些事情,所以首先让它做点什么。

int calcCipherColumn(char keyWord[25], char cipherText[17424],
                     int rowSize, char message[388]) 
{
    int keyColumn    = 0;
    int cipherColumn = 0;
    int offset       = 1;
    int nextWord     = 1;

    int lengthOfWord   = 0;
    int lengthOfCipher = 0;

    lengthOfWord = length(keyWord);
    lengthOfCipher = length(cipherText);

    while (keyWord[keyColumn] != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyWord[keyColumn + offset] 
        != cipherText[cipherColumn + (rowSize*nextWord) + nextWord]) {
            cipherColumn++;
            continue;
        }
    }
    return cipherColumn;
}

在那里,现在它正在执行某些操作。

现在,它可以执行操作了,我可以看到可以摆脱的东西了。这种长度的东西甚至没有使用。这continue也不做任何事情。

int calcCipherColumn(char keyWord[25], char cipherText[17424],
                     int rowSize, char message[388]) 
{
    int keyColumn    = 0;
    int cipherColumn = 0;
    int offset       = 1;
    int nextWord     = 1;

    while (keyWord[keyColumn] != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyWord[keyColumn + offset] 
        != cipherText[cipherColumn + (rowSize*nextWord) + nextWord]) {
            cipherColumn++;
        }
    }
    return cipherColumn;
}

让我们进行一些小的空白调整,因为我们生活在源代码控制的世界中,当行被报告为更改的唯一原因是因为它在做不同的事情,而不是因为它的一部分必须在列中对齐时,这很好。

int calcCipherColumn(char keyWord[25], char cipherText[17424],
                     int rowSize, char message[388]) 
{
    int keyColumn = 0;
    int cipherColumn = 0;
    int offset = 1;
    int nextWord = 1;

    while (keyWord[keyColumn] != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyWord[keyColumn + offset] 
        != cipherText[cipherColumn + (rowSize*nextWord) + nextWord]) {
            cipherColumn++;
        }
    }
    return cipherColumn;
}

是的,我知道它的可读性稍差,但否则,使用vdiff工具检测更改的用户会发疯。

现在,让我们修复这些愚蠢的换行符,因为我们试图保持在行长限制内。

int calcCipherColumn(
        char keyWord[25], 
        char cipherText[17424],
        int rowSize, 
        char message[388]
) {
    int keyColumn = 0;
    int keyOffset = 1;

    int nextWord = 1;
    int cipherColumn = 0;
    int cipherOffset = (rowSize * nextWord) + nextWord;

    char key = keyWord[keyColumn];
    char keyNext = keyWord[keyColumn + keyOffset];

    while (key != cipherText[cipherColumn]) {
        cipherColumn++;
        if (keyNext != cipherText[cipherColumn + cipherOffset]) {
            cipherColumn++;
        }
    }
    return cipherColumn;
}

在那里,循环中的逻辑现在专注于循环中的变化。实际上,除cipherColumn标记外的所有内容都可以标记final。嘿!看那个。我们现在有空间来做。

我要做的就是再添加3个变量,重命名一个变量,然后重新排列一下。结果恰好使得线足够短,可以适应而没有愚蠢的换行!=

确定名称keykeyNext没有那么描述性,但是它们每个仅使用一次,使用寿命不长,最重要的是,在循环中没有做任何有趣的事情。因此,它们不必是。通过引入额外的变量,我们现在可以根据需要将其名称加长。事情发生了变化,所以最终我们可能需要这样做。如果这样做,我们有呼吸的空间就很好。

我还自由地向您展示了Jeff Grigg的形式6变体样式,布局了输入参数以遵守行长限制。


哇,是描述性的!是的,我知道代码实际上并没有执行任何操作,我可能应该发布的内容不只是一小段,但我想我试图了解专业人员在代码列长度和变量名称方面的一般想法,但是您的回答显示了一些非常甜蜜的更改,从现在开始,我一定会在代码中实现这些更改!我还有一个问题:您认为在哪里适合换行?在运营商之前?是否有公认的“标准”?
RaulT

1
@RaulT花一些时间阅读您正在使用的任何代码库。它将使您知道可以使用什么,不会令其他编码人员感到惊讶。如果有的话,请遵循标准文件。但是最好的办法是问其他程序员,并问他们您的资料可读性如何。哦,看看codereview.stackexchange.com
candied_orange

我将在cipherOffset下添加一条注释并解释该计算,因为该公式并不明显。您将忘记为什么在三周内。
尼尔森

15

其他人已经提出了一些有用的建议,让我总结一下:

  • 每行80个字符可能是80年代的黄金法则。如今,大多数人都认为100到130个字符是可以的。
  • 在表达式中使用换行符。
  • 通过引入中间结果来拆分冗长的表达式。

我想补充一点建议:不要对长名信条!变量的范围越大,必须在其名称中输入更多信息。通常,保持变量范围较小是一个好主意。

例如,如果您在关键字加密表的列中有一个变量,并且很明显在变量的范围内只使用了一个表,则可以将其命名为column甚至col。如果范围更大并且涉及多个表,则称它为keyWordEncryptionTableColumn

另一个示例:如果循环的主体跨越两三行,并且必须使用索引来访问数组的元素,则调用index并没有错i。在这种情况下,它比(例如)更具可读性(至少对于大多数人而言)arrayIndexOfMyVerySpecialDataSet


1
我同意你的回答。在工作中,出于遗留原因以及由于使用了审阅板,我们将80个字符/行用于c / c ++。对于C#100个字符/行,有时我会违反规则,并略超过100个以保持可读性。
peval27年

哇,真是个好答案!所有这些可回答的问题都很棒,感谢我的帮助!
RaulT

我坚决赞成按80个字符行已过时的想法。它仍然适用于某些项目和场所(主要是为了保持一致性),但是对于许多项目来说,这完全是不必要的。许多开发人员在完整的监视器上使用Visual Studio或IntelliJ之类的东西,并为其他内容(文档等)使用第二个监视器。因此,他们的代码占用了大量屏幕空间。如果每行仅使用80个字符,则可能有大量未使用的空间。80个字符的限制会伤害您!特别是当您考虑到标准库可以强制使用长屁股名称时。
Kat

1
我的观点是,在某些语言中,确实无法避免80个字符是一个很大的限制。那么为什么会不必要地使用它呢?还要提到的是,如今,几乎所有知名编辑器和IDE都具有出色的智能软包装功能,这使您完全不必限制行长。您可以让行长成为读者可以看到的。他们可以调整窗口大小以获得更好的结果。我个人发现这种方法有时是理想的。而且,对于这种软包装的工作方式,我还没有感到失望。

只要使用简单的变量名,就必须100%确定范围。我花了三个小时来学习JavaScript封闭。
尼尔森

3

我一般都同意你的老师。但是,如果您有一个要在大量失败的代码中大量使用的变量,那么在明确说明其含义之后,最好使用简写形式。就像当您有很多复杂的算术表达式和赋值时,使用长变量名时,它们的理解效果不佳。

关于概述:

ExtractMessage(char keyWord[25], char cipherText[17424],
               int rowSize, char message[388]) 

这没有任何意义,仅限制行长度不会使其更具可读性。如果您希望它可读,请执行以下操作:

ExtractMessage(
  char keyWord[25],
  char cipherText[17424],
  int rowSize,
  char message[388]
  )
{

然后,您甚至可能想要对齐类型标识符(在int之后添加一个空格)。但是,请谨慎/限制概述以下内容的初始化或参数列表:

int keyColumn    = 0;
int cipherColumn = 0;
int offset       = 1;
int nextWord     = 1;

问题是当您更改名称或添加变量时,可能必须重新格式化整个块以保持美观。它所做的工作不如您要引入的无意义的更改多,它在版本控制系统中看起来很可怕。您的同事将看到您更改了文件,并与以前的版本进行了比较,以查看您所做的工作。然后,每条线都会随着更改而点亮,从而掩盖了真正的更改。这将在一定程度上取决于所使用的比较工具的质量,但实际上这有多糟糕,但是通常来说,使代码过于个人化和/或使一行的格式依赖于另一行不是一个好主意。

有时,概述可以达到目的,如果您有数十条几乎相同的连续线,那么只要勾勒出轮廓,就很容易发现它们的不同之处。

请注意,工作场所可能正在进行一些自动格式化,这将根除您对代码所做的任何奇特格式化,然后再将其提交给版本控制系统。


1
就我个人而言,答案中的第一个代码块比第二个对我而言更具可读性。
Miles Rout

1
永远不要做第三件事,这是维护它的噩梦。
jwenting

3

免责声明:为了使我的观点更加清楚,我在这里有些夸张。因此,将任何最高级的盐都服食。

您的老师是100%正确的:不再有大约80个字符的“黄金法则”(除非您正在编写linux代码)。建立该规则是因为当时终端的大小,但是如今,您可以轻松地在终端窗口中填充150个以上的字符。即使您超过该限制,您的编辑器也希望可以自动换行,因此您无需滚动。不超过80个字符的唯一原因是需要滚动

就是说,确实有必要不要让您的台词无限期地增长。线越长,人解析起来就越难。但是简短的变量名并不是解决长行问题的补救办法

补救措施是通过引入更正确命名的变量来逻辑上拆分表达式。不要试图对空格聪明。只需标识任何可以适当命名的子表达式,然后为其创建变量即可。这样既简化了计算变量的代码,也简化了使用该变量的代码


这不是您问题的一部分,但无论如何,我还是要发表评论:垂直对齐=操作员是一个非常糟糕的主意。

这有三个原因:

  1. 编辑包含垂直指定的运算符的块是PITA。每当最大变量的长度发生更改(重命名,添加,删除)时,都需要修饰块中的所有行,以重新获得“漂亮”的布局。

    当然,可以通过使用胜任的编辑器来减轻此问题,这是次要原因。真正的原因是第二个:

  2. 通过重新对齐引入的那些虚假的空格更改在像的现代版本控制系统中效果不好git。它们往往会在没有发生实际冲突的情况下创建大量合并冲突,并且如果不使用对齐方式,则不会发出任何冲突信号。这些虚假的冲突中的每一个都会使您花费宝贵的时间,而一劳永逸

  3. 对齐方式具有零语义含义。这是毫无意义。通过对齐,您无法更好地理解任何内容。您块中的每一行都需要自己阅读以了解其功能,与上下两行的连接纯粹是句法性质。

由于对齐方式不具有任何语义含义,但是会产生可观的成本,因此您应该先习得该习惯,然后再花费更多时间。


如果您非常喜欢80个字符的限制,请尝试一些fortran编程。的确,较新的标准将fortran行数限制提高到132个字符,但它仍然有效,因为它破坏了超出该限制的任何程序的编译。如果您擅长编程,那么您很快就会讨厌fortran,包括它的行长限制。之后,您将在余生中得到治愈。

我不得不自己专业地进行一些fortran编程,我告诉你,它教会了我最讨厌该行长度限制的问题。绝对没有什么比只因为编译器将不再正确地编译它而将原本简单易读的行分成几部分更令人沮丧的了。而且肯定有几行代码以一行表示时最简单。


3

由于编程环境的限制,多年来出现了许多样式约定(不是规则!)。早在打孔卡时代,您就对可能出现在物理源代码行上的字符数进行了 硬性限制(这就是为什么Fortran保留第6列作为行连续字符的原因)。并不是很多年前,我就在研究80x24琥珀色黑色VT220终端。虽然我使用的编辑器没有将行数限制为80个字符,但是如果您尽力避免水平滚动,则生活会容易很多。

在较早版本的Fortran(直到'77,IINM)之前,您甚至不能拥有超过6到8个字符长的标识符。即使到了80年代末,C都只能保证外部名称中的前8个字符是有意义的(这就是为什么某些库函数具有诸如的奇妙描述性名称的原因strpbrk)。

当然,进入21世纪的二十年,我们已经没有那些限制了。没有理由使用更多描述性标识符。

事情是在适当的情况下,i并且j并且k完全合理的,有意义的名称。如果我要遍历循环中的数组或向量,而只需要一些东西来标识当前元素,则i效果很好。我不会使用这样的名称currentElement- 在这种情况下它不再有意义,它只会增加视觉混乱感。

说了这么多,你的老师并没有错迫使你去思考为更长,更具描述性的名称方面的一切 -生活会为你更容易,如果你进入这个习惯,然后再学会在哪里节约是必要的。正如有人谁在被迫适应一切为8个字符或更少有一段时间,这绝对是更好地犯错就小于更多信息,一边。随着经验的积累,您将学到可以节省标识符长度的地方,以及需要更多描述的地方。


-1

我不确定这是否适用于c,但是是否可以将公式拆分为多行?我知道python存在类似的东西。

看看是否可以在新行上开始+(rowSize * nextWord)+ nextWord]){ (就像在IDE中按Enter键,然后查看它是否缩进,以便C知道当前行中的前一个表达式已完成)


1
是的,这绝对有可能。C会识别行和代码行,直到您添加类似分号的内容为止。这样做的问题是我们的函数不能超过50行,尽管我的示例代码没有50行,但这仅占我总函数的一小部分。我觉得在50 x 80的盒子中编写具有很大意义的变量的算法非常受限制,这些变量也可以执行我需要的功能。我可以继续将这些长长的代码块存储到新函数中,但是我觉得我最终将获得太多的函数调用,人们将无法阅读代码。
RaulT

5
“我觉得我将要结束这么多的函数调用,人们将无法阅读代码。” 恰恰相反!将代码提取到单独的方法中,可以为它们提供描述性名称,以提高可读性(尤其是从中提取方法的可读性)。如果最终使用过多的方法,则您的类可能会做很多事情(单职责原则)。再次将方法提取到一个单独的类中,可以为该名称指定一个描述性名称。
罗曼·赖纳

接近50行的任何函数都可能太长和太复杂(数据初始化可能例外,复杂度为1),但是通常在讨论类似限制时,它们是代码行而不是文本行,因此将单个行拆分代码行,即分号通常不会算作多余的行,请与Prof!联系!
史蒂夫·巴恩斯
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.