我不是真的写大型项目。我不是在维护庞大的数据库,也不是在处理数百万行代码。
我的代码主要是“脚本”类型的东西-用于测试数学函数或模拟的东西-“科学编程”。到目前为止,我从事的最长程序是几百行代码,而我从事的大多数程序都在150左右。
我的代码也很糟糕。前几天,当我试图找到我之前写的文件时,我意识到了这一点,但是我可能改写了,并且我没有使用版本控制,这可能使许多人对我的愚蠢感到痛苦。
我的代码风格令人费解,并填充了过时的注释,这些注释指出了执行某项操作的替代方法或复制了几行代码。尽管变量名总是以非常好的描述性开始,但是当我按照某人想要测试的新内容进行添加或更改时,代码却被覆盖在顶部并被覆盖,因为我觉得这件事应该立即进行测试。有一个框架,我开始使用cr脚的变量名,文件存放到锅中。
在我现在正在开发的项目中,我正处于所有这些都重新吸引我的阶段。但是问题是(除了使用版本控制,以及为每个新迭代创建一个新文件并将其全部记录在某个地方的文本文件中,这可能会极大地改善这种情况)我真的不知道如何进行改进我实际的编码风格。
编写较小的代码段是否需要进行单元测试? OOP怎么样?相对于处理较大的项目,什么样的方法适合在进行“科学编程”时快速编写优质,清晰的代码?
我之所以问这些问题,是因为编程本身通常并不复杂。它更多地是关于我正在使用程序进行测试或研究的数学或科学。例如,当两个变量和一个函数可能会照顾到它时,是否需要一个类? (考虑到通常情况下,程序的速度也应优先考虑更快的情况-当您运行25,000,000+个仿真的时间步长时,您一定会希望这样做。)
也许这太宽泛了,如果是的话,我对此表示歉意,但是在阅读编程书籍时,通常似乎是在大型项目中解决了这些问题。我的代码不需要OOP,而且已经很短了,所以它不像“哦,但是如果这样做的话,文件将减少一千行!”我想知道如何在这些较小,更快的项目上“重新开始”并进行干净的编程。
我很乐意提供更多具体细节,但是我认为建议越笼统,就越有用。我正在用Python 3编程。

有人建议重复。让我明确地说,我并不是在谈论完全忽略标准编程标准。显然,存在这些标准是有原因的。但是,另一方面,如果可以完成一些标准的工作,编写写成OOP的代码真的有意义吗,因为它的简短性使它的编写速度更快,并且具有相似的可读性。程序?
有例外。此外,除了简单的标准外,可能还有科学编程的标准。我也在问那些。这与编写科学代码时是否应忽略常规编码标准有关,而是与编写干净的科学代码有关!

更新
只是以为我会添加“不超过一个星期以后”的更新。您的所有建议都非常有帮助。我现在使用版本控制-git,其中git kraken用于图形界面。它非常易于使用,并且彻底清理了我的文件-不再需要保留旧文件,也不再需要旧版本的代码注释掉“以防万一”。
我还安装了pylint并在所有我的代码。最初,一个文件的分数为负。我什至不知道那怎么可能。我的主文件开始于〜1.83 / 10,现在为〜9.1 / 10。现在,所有代码都非常符合标准。我还用自己的眼睛跑过去,更新了已经消失的变量名,嗯,很糟糕,并寻找要重构的部分。
特别是,我在这个网站上问了一个最近的问题,它重构了我的主要功能,现在变得更整洁,更短:不再是冗长,if肿的if / else填充功能,现在它的大小还不到一半,而且更容易弄清楚发生了什么。
我的下一步是实施各种“单元测试”。我的意思是一个可以在主文件上运行的文件,该文件使用assert语句和try / excepts来查看其中的所有功能,这可能不是最好的方法,并且会导致很多重复的代码,但我会继续阅读并尝试找出做得更好的方法。
我还大大更新了我已经编写的文档,并添加了诸如excel电子表格,文档和相关的补充文件到github仓库的文件。现在看起来有点像是一个真正的编程项目。
......我想这就是要说的:谢谢。

评论

科学代码是否可能重复?是否足以忽略共同的编码标准?

您可能会找到答案,是否值得为科学研究代码编写单元测试?有用。

如果您积极想要改进代码,则可以考虑在“代码审查”中发布一些内容。那里的社区将很乐意为您提供帮助。

当您说“除了使用版本控制,并为每个新迭代创建一个新文件并将其全部记录在文本文件中的某个位置”时,是“和”的意思是“或”,因为如果您使用的是版本控制,则不应不能复制粘贴版本。关键是版本控制为您保留所有旧版本

@mathreadler我认为您不太了解。是的,只有我可能会真正阅读并弄乱代码(尽管您永远不知道,而且我确实有一个与我合作的人可以编程,尽管使用的是不同的语言)...但是代码仍然废话我将不得不稍后阅读并再次弄清楚我在做什么。这是一个问题,我可以作证,因为我现在正在体验这种效果,而且由于实现了版本控制和此处建议的其他技术,事情变得更容易了。

#1 楼

对于科学家来说,这是一个非常普遍的问题。我已经看过很多次了,它总是源于这样一个事实,那就是编程是您可以选择用来做工作的工具。
所以您的脚本很乱。我要违背常识说,假设您是独自编程,那还不错!您再也不会接触到大部分您写过的东西,因此花太多时间来编写漂亮的代码而不是产生“值”(因此脚本的结果)对您无济于事。 />但是,有时您需要回到所做的事情,并确切地了解工作原理。此外,如果其他科学家需要检查您的代码,那么尽可能地简洁明了也很重要,这样每个人都可以理解它。
您的主要问题是可读性,所以这里有一些改进技巧:
变量名称:
科学家喜欢使用简洁的符号。所有数学方程式通常都使用单个字母作为变量,看到您的代码中有很多非常短的变量,我不会感到惊讶。这极大地损害了可读性。当您返回到代码时,您将不会记住y,i和x2所代表的含义,并且您将花费大量时间来弄清楚它。尝试使用确切代表变量的名称来明确命名变量。
将代码拆分为函数:
现在,您已对所有变量进行了重命名,方程看起来很糟糕,而且多行。 br />不要将该方程式留在您的主程序中,而是将其移动到其他函数中,并相应地命名。现在,您将不再需要编写大量混乱的代码,而是使用简短的说明,告诉您正在发生的事情以及所使用的方程式。这不仅可以改善您的主程序,因为您甚至不必查看实际的方程式就可以知道自己做了什么,还可以改善方程式代码本身,因为在单独的函数中,您可以根据需要命名变量,然后返回更熟悉的单个字母。
按照这种思路,尝试找出代表某物的所有代码段,尤其是如果某物是您必须在代码中多次执行的事情,然后将其拆分为职能。您会发现您的代码将很快变得易于阅读,并且无需编写更多代码就可以使用相同的函数。
锦上添花,如果您的更多代码中需要这些函数程序,您可以为它们创建一个库,并使它们始终可用。
全局变量:
回到我刚开始的时候,我认为这是传递所需数据的一种好方法在我程序的许多方面事实证明,还有很多其他方法可以传递信息,而全局变量所做的唯一事情就是让人头疼,因为如果您进入程序的随机位置,您将永远不知道该值的最后使用或编辑时间,并且追踪它会很痛苦。尝试尽可能避免它们。
如果您的函数需要返回或修改多个值,请使用这些值创建一个类并将其作为参数传递下来,或者使该函数返回多个值(使用命名元组),然后在调用者代码中分配这些值。
版本控制
这不会直接提高可读性,但是可以帮助您完成上述所有操作。每当您进行一些更改时,请进行版本控制(本地Git存储库就足够了),如果某些操作不起作用,请查看您所做的更改或回滚!这将使您的代码重构更加容易,并且在您不小心破坏东西的情况下将成为一个安全网。
牢记所有这些将使您能够编写清晰,更有效的代码,还可以帮助您更快地发现可能的错误。 ,因为您不必费解巨大的功能和混乱的变量。

评论


极好的建议。但是,我不能对“还不错”的评论表示赞同。太糟糕了低质量的科学脚本是数据分析可重复性的主要问题,也是分析错误的常见来源。编写好的代码不仅很有价值,以便以后您可以理解它,而且也可以避免一开始就出错。

–杰克·艾德利(Jack Aidley)
18年7月6日在13:24

如果代码是一个众所周知的公式的实现,那么单字母变量名称以及类似的名称可能是正确的选择。取决于听众...更重要的是,是否希望读者已经知道名称的含义。

– cHao
18年7月6日在14:27

@cHao问题不是在公式中插入了这些变量(因此“在函数内将其重命名”建议),而是在变量外对其进行读取和操作时以及当它们开始与其他变量冲突时(例如,我已经看到需要三个“ x”变量的人将它们命名为x1,x2,x3)

– BgrWorker
18年7月6日在14:37

“我会违背常识……”不,你不是。您要违背普遍的教条,这本身就是违反常识的。 ;)这都是完美的建议。

– jpmc26
18年7月6日在22:52



作为(前)科学程序员,我通常采用三个规则。如果我最后写了三遍类似的代码,则该功能会充实并写在带有文档的单独模块中(通常只是注释,但这足够了)。这限制了即席编程的混乱,并允许我建立一个将来可以扩展的库。

–rcollyer
18年7月9日在14:39

#2 楼

物理学家在这里。到过那里。

我认为您的问题不在于工具的选择或编程范式(单元测试,OOP等)。这是关于态度的心态。您的变量名一开始就被很好地选择了,但最终却被胡扯了,这一事实充分说明了这一点。如果您认为代码中的代码“运行一次,然后扔掉”,那将不可避免地是一团糟。如果您将其视为手工艺和爱的产物,那么它会很美丽。

我相信只有一种编写清晰代码的方法:为它编写
要阅读它的人,而不是要运行它的解释器的人。解释器不在乎您的代码是否是
,但人类读者会在乎。

您是科学家。您可能会花费大量时间来完善
科学文章。如果您的初稿看起来很复杂,您将
重构它,直到逻辑以最自然的方式流动为止。您想
您的同事阅读它,并找到清晰的论点。您
希望您的学生能够从中学习。

编写清晰的代码是完全一样的。将您的代码视为对算法的详细说明,该算法只是偶然发生在机器可读的范围内。想象一下,您打算将其发布为人们会阅读的文章
。您甚至要在会议上展示它,并
逐行引导观众。现在排练您的
演示文稿。是的,逐行!不好意思,不是吗?所以
清理幻灯片(错误,我的意思是您的代码),然后再排练。
重复直到对结果满意为止。

它仍然会排练之后,如果您可以向真实的人展示您的代码,而不仅仅是虚构的人和您的未来自己,那么更好。一行一行地经过称为“代码遍历”,这不是愚蠢的做法。

当然,所有这些都是有代价的。编写清晰的代码比编写一次性代码要花很多时间。只有您可以评估
好处是否超过您特定用例的成本。

关于工具,我之前说过它们并不那么重要。但是,
如果必须选择一个,我会说版本控制是最有用的。

评论


«编写清晰的代码与编写清晰的文章完全相同。»我完全赞同,很好!

– juandesant
18年7月6日在13:21

这是大多数专业程序员忘记说的东西,因为它是如此明显。当代码可以被其他程序员读取和修改时,您的代码即告完成。不能在运行时产生正确的输出。 OP需要花费每个脚本花费额外的时间来重构和注释他的代码,以使其易于阅读。

– UEFI
18年7月6日在13:29

尽管编写干净的代码确实比编写一次性代码花费更多的时间,但更重要的是,读取一次性代码比读取干净的代码要花费更多的时间。

–user949300
18年7月6日在16:02

@UEFI不,大多数专业程序员都没有意识到这一点。还是不在乎。

– jpmc26
18年7月6日在23:00



同意100%。统计学家变成了程序员,所以我在工作中做了大量的“科学”编程。当您必须在1、4或12个月后返回该代码时,清晰的代码和有意义的注释将是您的救命稻草。阅读代码可以告诉您代码在做什么。阅读注释会告诉您代码应该执行的操作。

–railsdog
18年7月7日在3:20

#3 楼

版本控制可能会给您带来最大的收益。它不仅用于长期存储,还可以跟踪您的短期实验并返回到上一个可用的版本,并在整个过程中保持记录。

下一个最有用的是单元测试。关于单元测试的事情是,甚至具有数百万行代码的代码库一次都进行了单元测试一个功能。单元测试是在最小的最低抽象级别上完成的。这意味着为小型代码库编写的单元测试与用于大型代码库的单元测试在根本上没有区别。有更多。

单元测试是最好的方法,它可以防止在修复其他问题时破坏已经起作用的东西,或者至少在执行时告诉您。当您不像程序员那样熟练时,或者不知道如何或不想编写更多冗长的代码以使错误的可能性降低或变得更加明显时,它们实际上会更有用。

之间在进行版本控制和编写单元测试时,您的代码自然会变得更加干净。达到平稳状态后,还可以学习其他更清晰的编码技术。

评论


我虔诚地对我的大部分代码进行单元测试,但发现单元测试是探索性的,科学代码也没什么用。从根本上讲,这种方法似乎不起作用。我不认识该领域中的任何计算科学家对他们的分析代码进行单元测试。我不确定造成这种不匹配的原因是什么,但是原因之一当然是,除了琐碎的单元之外,很难或不可能建立好的测试用例。

–康拉德·鲁道夫(Konrad Rudolph)
18年7月6日在11:30



在这些情况下,@ KonradRudolph的窍门可能是将代码中具有明确定义的行为(读取此输入,计算该值)的部分与真正探究或正在适应的部分(例如,一些人类可读的输出或可视化。这里的问题很可能是,关注点分离不良会导致这些线模糊不清,从而导致人们认为在这种情况下不可能进行单元测试,从而使您回到重复周期的起点。

– Ant P
18年7月6日在12:19

顺便说一句,由于LaTeX文档的格式易于进行文本区分,因此版本控制也非常适用于LaTeX文档。这样,您就可以为您的论文和支持它们的代码建立一个存储库。我建议研究分布式版本控制,例如Git。学习曲线有些许麻烦,但是一旦您了解了它,就可以找到一种很好的简洁方法来迭代您的开发,并且您可以使用一些有趣的选项来使用像Github这样的平台,该平台为学者提供免费的团队帐户。

–丹·布莱恩特
18年7月6日在13:20



@AntP可能没有太多代码可以有意义地重构为定义良好的可测试单元。本质上,许多科学代码正在将一堆库集中在一起。这些库已经过了良好的测试,结构清晰,这意味着作者仅需编写“胶水”,以我的经验,几乎不可能为非重言式的胶水编写单元测试。

–James_pic
18年7月6日在15:54



“在进行版本控制和编写单元测试之间,您的代码自然会变得更加干净。”这不是真的。我可以亲自证明。这两种工具都不能阻止您编写糟糕的代码,尤其是在糟糕的代码之上编写糟糕的测试只会使清理工作变得更加困难。测试不是万能的灵丹妙药,对于任何仍在学习的开发人员(每个人)来说,像进行测试一样说话是一件可怕的事情。但是,版本控制通常不会像不良测试那样对代码本身造成损害。

– jpmc26
18年7月6日在19:01



#4 楼


(除了使用版本控制,并为每个新迭代创建一个新文件并将其全部记录在某个地方的文本文件中,这可能会极大地改善这种情况)


您可能会自己解决这个问题,但是如果您必须“将所有内容都记录在某个地方的文本文件中”,则说明您无法充分利用版本控制系统。使用Subversion,git或Mercurial之类的东西,并在每次提交时写一个好的提交消息,您将获得一个日志,该日志可用于文本文件,但不能与存储库分开。

除此之外,使用版本控制是您可以做的最重要的事情,原因是现有答案都没有提到:结果的可重复性。如果您既可以使用日志消息,也可以在结果中添加带有修订号的注释,则可以确保能够重新生成结果,并且最好将其随论文一起发布。


编写较小的代码是否需要单元测试? OOP怎么样?什么样的方法对于进行“科学编程”(而不是在较大的项目上)时,可以快速地编写清晰的代码?


单元测试从不需要,但是如果(a)代码足够模块化,可以测试单元而不是整个单元; (b)您可以创建测试。理想情况下,您可以手动编写预期的输出,而不用代码生成它,尽管通过代码生成它至少可以为您提供回归测试,该测试可以告诉您某些事物是否改变了其行为。只需考虑测试是否比正在测试的代码更容易出错。

OOP是一种工具。如果有帮助,请使用它,但这不是唯一的范例。我假设您只真正了解过程编程:如果是这种情况,那么在所描述的上下文中,我认为与OOP相比,您可以从研究函数式编程中受益更多,尤其是在可能的情况下避免副作用的学科。 Python可以以非常实用的样式编写。

评论


+1提交消息;它们就像不能过时的注释,因为它们在实际适用时已与代码版本绑定在一起。要了解您的旧代码,浏览项目的历史记录(如果更改以合理的粒度提交)比阅读过时的注释要容易得多。

–愚蠢的怪胎
18年7月6日在22:00



Subversion,git和Mercurial不可替代。我强烈建议在Subversion的本地存储库中使用Git(或Mercurial)。使用单独的编码器,Subversion的缺陷已不再是问题,但它并不是协作开发的好工具,并且可能在研究中发生

– mcottle
18年7月9日在5:24

@mcottle,我个人更喜欢git,但是我不认为这是讨论差异的合适位置,特别是因为选择是一场活跃的宗教战争之一。鼓励OP使用某些东西要比吓跑他们远离该地区更好,而且这种决定在任何情况下都不是永久的。

– Peter Taylor
18年7月9日在6:26

#5 楼

在研究生院,我自己写了一些算法繁重的代码。这有点难以破解。粗略地讲,许多编程约定都是围绕以下思想进行的:将信息放入数据库,在适当的时间检索它,然后对该数据进行按摩以将其呈现给用户,通常使用库来进行任何数学运算或运算。该过程中算法繁重的部分。对于这些程序,您所听说的有关OOP的一切,将代码分解为短函数以及使所有内容在可能的情况下一目了然都是很好的建议。但这不适用于算法繁重的代码或实现复杂数学计算的代码。

如果您正在编写脚本来执行科学计算,则可能有包含等式的论文或您在其中编写的算法。如果您使用的是自己发现的新想法,则希望将它们发布在自己的论文中。在这种情况下,规则是:您希望您的代码阅读尽可能像已发布的方程式。这是有关Software Engineering.SE的答案,有200多个投票支持这种方法并解释其含义:短变量名有借口吗?

作为另一个示例,有一些很棒的摘要Simbody中的代码,这是一种用于物理研究和工程的物理模拟工具。这些摘录带有注释,其中显示了要用于计算的方程式,然后是代码,其代码读取的内容与所实现的方程式尽可能接近。

ContactGeometry.cpp



 // t = (-b +/- sqrt(b^2-4ac)) / 2a
// Discriminant must be nonnegative for real surfaces
// but could be slightly negative due to numerical noise.
Real sqrtd = std::sqrt(std::max(B*B - 4*A*C, Real(0)));
Vec2 t = Vec2(sqrtd - B, -sqrtd - B) / (2*A);
 


ContactGeometry_Sphere.cpp

 // Solve the scalar Jacobi equation
//
//        j''(s) + K(s)*j(s) = 0 ,                                     (1)
//
// where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
// with respect to the arc length s. Then, j is the directional sensitivity and
// we obtain the corresponding variational vector field by multiplying b*j. For
// a sphere, K = R^(-2) and the solution of equation (1) becomes
//
//        j  = R * sin(1/R * s)                                        (2)
//          j' =     cos(1/R * s) ,                                      (3)
//
// where equation (2) is the standard solution of a non-damped oscillator. Its
// period is 2*pi*R and its amplitude is R.

// Forward directional sensitivity from P to Q
Vec2 jPQ(R*sin(k * s), cos(k * s));
geod.addDirectionalSensitivityPtoQ(jPQ);

// Backwards directional sensitivity from Q to P
Vec2 jQP(R*sin(k * (L-s)), cos(k * (L-s)));
geod.addDirectionalSensitivityQtoP(jQP);
 


评论


加上一个使代码“尽可能地阅读已发布的方程式”。抱歉,长而有意义的变量名的提倡者。科学代码中最有意义的名称通常是讨厌,简短和粗暴的,正是因为这恰恰是该代码试图实现的科学期刊论文中使用的约定。对于执行在期刊论文中找到的方程式的大量方程式代码,通常最好尽可能与论文中的术语保持密切联系,如果这与良好的编码标准背道而驰,那将是艰难的。

–David Hammen
18年7月8日在15:39

@DavidHammen:作为研究生,我尊重这一点。作为程序员,我坚持认为,每个函数的顶部都有一个巨大的注释块,以纯英语(或您选择的语言)描述每个变量的含义,即使只是一个临时占位符。这样,我至少可以参考一下。

–tonysdg
18年7月9日在17:24

@DavidHammen此外,Python对源文件中的UTF-8的支持以及变量名称的简单规则使声明λ或φ而不是声明丑陋的lambda_或phy变得容易。

–301_Moved_Permanently
18年7月9日在22:57

@tonysdg您已经有参考;称为“ Hammen等人(2018)”(或其他名称)。它将比任何注释块更详尽地解释变量的含义。保持变量名称与论文中的符号接近的原因恰恰是为了使其更容易将论文中的内容与代码中的内容联系起来。

–没人
18-10-10在14:44

#6 楼

因此,我的日常工作是为加利福尼亚大学系统研究数据的发布和保存。有几个人提到了可重复性,我认为这实际上是这里的核心问题:以记录其他人需要复制实验的方式来记录代码,理想情况下,编写使其他人都可以直接使用的代码重现您的实验并检查您的结果是否有错误源。

但是我没有提到的我认为很重要的一点是,资助机构越来越多地将软件发行视为

为此,如果您想要针对研究人员而不是普通软件开发人员的特定内容,我不推荐该软件木工组织足够高度。如果您可以参加他们的工作坊之一,那就太好了;如果您有时间/可以做的事情是阅读他们有关科学计算最佳实践的一些论文,那也很好。从后者:


科学家通常出于这些目的开发自己的软件,因为这样做需要大量特定领域的知识。结果,最近的研究发现,科学家通常花费30%或更多的时间来开发软件。但是,其中90%或更多的人主要是自学成才,因此缺乏基本软件开发实践的经验,例如编写可维护的代码,使用版本控制和问题跟踪器,代码审查,单元测试和任务自动化。

我们认为软件只是另一种实验设备,应该像任何物理设备一样仔细地构建,检查和使用软件。但是,尽管大多数科学家都在谨慎地验证他们的实验室和现场设备,但大多数人并不知道其软件的可靠性。这可能导致严重错误,影响已发表研究的主要结论。 …

另外,由于软件通常用于单个项目之外,并且经常被其他科学家重用,因此计算错误会对科学过程产生不成比例的影响。当直到发布后才发现来自另一个组的代码的错误时,这种级联的影响导致了几个明显的缩回。


他们所建议的做法的简要概述:


为人而不是计算机编写程序
让计算机来工作
进行增量更改
不要重复自己(或其他人)
计划出现错误
仅在软件正常工作后对其进行优化
文档设计和目的,而不是机制上的优化
协作

本文对上述各点进行了详细介绍。

#7 楼


如果可以完成某些标准的事情,写OOP的代码真的有意义吗?它会比
编写的要快得多,并且具有相似的可读性是因为程序的简短性吗?


个人回答:
我也为科学目的编写了很多脚本。对于较小的脚本,我只是尝试遵循一般的良好编程习惯(即使用版本控制,使用变量名进行自我控制)。如果我只是为了快速打开或可视化数据集而写东西,那我就不用为OOP烦恼了。但是,如果您想弄清楚何时使用编程概念或范式,则需要考虑以下几点:


可伸缩性:脚本会独立存在还是会独立存在?最终在更大的程序中使用?如果是这样,是否使用OOP进行较大的编程?可以将脚本中的代码轻松集成到较大的程序中吗?
模块化:通常,您的代码应该是模块化的。但是,OOP以一种非常特殊的方式将代码分成多个块。这种类型的模块化(即将您的脚本分解成类)对您的工作有意义吗?


我想知道如何“重新开始”并在这些基础上进行干净编程
规模较小,速度更快的项目。


#1:熟悉其中的内容:
即使您只是“脚本”(而且您实际上只是在乎科学部分),您应该花一些时间来了解不同的编程概念和范例。这样,您可以更好地了解应该/不应该使用什么以及何时使用。这听起来有些令人生畏。您可能还会有一个问题,“我从哪里开始/我应该从哪里开始?”我尝试在接下来的两个要点中解释一个良好的起点。

#2:开始修复您所知道的错误:
就个人而言,我将从我知道错的事情开始。获得一些版本控制,并开始管教自己,以使用这些变量名变得更好(这是一次艰巨的努力)。解决您所知道的错误可能听起来很明显。但是,根据我的经验,我发现固定一件事会使我走向另一件事,依此类推。在不知不觉中,我揭露了我做错的10件事,并弄清楚了如何解决它们或如何以干净的方式实现它们。

#3:获得编程合作伙伴:
如果“重新开始”不涉及参加正式的课程,请考虑与开发人员合作并要求他们检查您的代码。即使他们不了解您正在做的事情的科学部分,他们也可能会告诉您您可以做些什么来使您的代码更加优雅。

#4:寻找财团:
,我不知道您所处的科学领域。但是,根据您在科学界的工作,请尝试寻找财团,工作组或会议参与者。然后查看他们是否正在制定任何标准。这可能会导致您使用一些编码标准。例如,我做了很多地理空间工作。通过查看会议论文和工作组,我来到了开放地理空间联盟。他们要做的事情之一就是制定地理空间开发标准。

希望对您有所帮助!




侧面说明:我知道您只是以OOP为例。我不想让您认为我在如何使用OOP编写代码方面陷入困境。编写一个继续该示例的答案会更容易。

评论


我认为#3是最重要的问题-经验丰富的程序员可以告诉OP他们需要的概念(#1),如何以更好的方式组织脚本以及如何使用版本控制(#2)。

–布朗博士
18年7月6日在5:44



#8 楼

我建议坚持Unix原则:保持简单,愚蠢! (吻)

或者换一种说法:一次做一件事情,做得好。

是什么意思?好吧,首先,这意味着您的功能应该简短。几秒钟之内无法完全理解其目的,用法和实现的任何功能都太长了。它可能同时执行多项操作,每项操作都应根据自身情况进行。因此,将其拆分。

就代码行而言,我的启发是10行是一个很好的功能,超过20行最有可能成为废话。其他人有其他启发式方法。重要的是使长度尽可能短,以便您可以立即掌握。

如何拆分长函数?好吧,首先您要寻找重复的代码模式。然后,将这些代码模式分解出来,给它们一个描述性的名称,并观察代码的缩小。确实,最好的重构是减少代码大小的重构。

当使用复制粘贴编程所讨论的函数时,尤其如此。每当您看到这样的重复模式时,您都会立即知道应该将其转变为它自己的功能。这是“不要重复自己”(DRY)的原则。每当您遇到复制粘贴时,您就在做错事!而是创建一个函数。


Anecdote
我曾经花了几个月的时间重构代码,每个代码都有大约500行的功能。完成后,总代码短了大约一千行;我在代码行方面产生了负输出。我欠公司(http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html)。尽管如此,我仍然坚信这是我做过的最有价值的作品之一...


某些功能可能会很长,因为它们会陆陆续续地做一些不同的事情。这些不是DRY违规,但也可以拆分。结果通常是一个高级函数,该函数调用实现原始函数各个步骤的少数函数。通常,这将增加代码的大小,但是添加的函数名称在使代码更具可读性方面起作用。因为现在您有了一个顶层函数,其所有步骤都被明确命名。同样,在此拆分之后,可以清楚地知道哪个步骤对哪些数据进行操作。 (函数自变量。您不使用全局变量,对吗?)

此类分段函数拆分的一种很好的启发方法是,每当您试图编写一个分段注释时,或者当您找到一个分段时在您的代码中注释。这很可能是应该拆分功能的要点之一。本节的注释也可以为新功能起一个名字。

KISS和DRY原理可以使您走很长一段路。您不需要立即开始使用OOP等,通常情况下,只需应用这两种方法,就可以大大简化操作。但是,从长远来看,了解OOP和其他范例确实会有所收获,因为它们会为您提供其他工具,使您可以使程序代码更清晰。

最后,用承诺。您将某些因素分解到一个新函数中,那就是提交。您将两个函数融合为一个,因为它们确实在做同一件事,那就是提交。如果重命名变量,则为提交。经常提交。如果一天过去了,但您没有承诺,那么您可能做错了什么。

评论


关于拆分长方法的要点。关于轶事之后第一段的另一个很好的启发式方法:如果您的方法可以在逻辑上划分为多个部分,并且您倾向于编写注释来解释每个部分的功能,则应在注释处将其分解。好消息,这些评论可能使您对如何使用新方法有了一个很好的了解。

– Jaquez
18年7月7日在4:33

@Jaquez Ah,完全忘记了那个。谢谢你提醒我。我已经更新我的答案,以包括此:-)

–cmaster-恢复莫妮卡
18年7月7日在8:36

要点,我想简单地说一下“ DRY”是最重要的单个因素。识别“重复”并将其删除是几乎所有其他编程构造的基石。换句话说,所有编程结构都至少部分存在,以帮助您创建DRY代码。首先说“永远不要复制”,然后练习识别和消除它。对可能重复的内容非常开放-即使它不是相似的代码也可能会重复功能...

– Bill K
18年7月10日在19:01

#9 楼

我同意其他人的观点,即版本控制将立即解决您的许多问题。具体来说:


无需维护已进行的更改的列表,或具有大量文件副本等,因为这是版本控制所要处理的。
不会因覆盖等原因而丢失更多文件(只要您坚持基本操作即可;例如,避免“重写历史记录”)
不需要过时的注释,无效的代码等。案件”;一旦他们致力于版本控制,就可以随意批评他们。这可以感觉很自由!

我想说的不要太想了:只需使用git。坚持简单的命令(例如仅一个master分支),也许使用GUI,就可以了。作为奖励,您可以使用gitlab,github等进行免费发布和备份;)

我写此答案的原因是要解决您可能会尝试做的两件事,而这些事情我上面没有提到。首先是使用断言作为单元测试的轻量级替代方案。单元测试往往位于功能/模块/被测试对象之外:它们通常将一些数据发送到功能中,接收返回的结果,然后检查该结果的某些属性。通常,这是个好主意,但由于以下几个原因可能会带来不便(尤其是“扔掉”的代码):


单元测试需要确定将提供给哪些数据功能。该数据必须是现实的(否则,几乎没有一点要测试),它必须具有正确的格式,等等。
单元测试必须可以“访问”他们要声明的内容。特别是,单元测试不能检查函数内部的任何中间数据。我们将不得不将该功能分解为较小的部分,测试这些部分,然后将其插入其他位置。
单元测试也被认为与程序有关。例如,如果测试套件自上次运行以来发生了很大的变化,它们可能会变得“过时”,甚至可能有很多针对甚至不再使用的代码的测试。

断言没有具有这些缺点,因为它们是在程序正常执行期间进行检查的。特别是:


由于它们是作为常规程序执行的一部分运行的,因此我们可以使用实际的真实数据。这不需要单独的管理,并且(根据定义)是现实的并且具有正确的格式。
断言可以写在代码中的任何位置,因此我们可以将其放置在我们有权访问要检查的数据的任何位置。如果我们想测试函数中的某个中间值,我们可以将一些断言放到该函数的中间!
由于它们是内联编写的,因此断言不会与函数的“不同步”。代码的结构。如果我们确保默认情况下会检查断言,那么我们也不必担心它们会“过时”,因为下次运行程序时,我们将立即查看它们是否通过!

您提到速度是一个因素,在这种情况下,断言检查在该循环中可能是不可取的(但对于检查设置和后续处理仍然有用)。但是,几乎所有的断言实现都提供了一种将其关闭的方法。例如,在Python中,显然可以通过使用-O选项运行来禁用它们(我不知道这一点,因为我之前从未感觉过需要禁用我的任何断言)。我建议您默认保留它们;如果您的编码/调试/测试周期变慢,则最好使用较小的数据子集进行测试,或者在测试过程中执行较少的模拟迭代,等等。如果由于性能原因而最终在非测试运行中禁用了断言,那么我建议您要做的第一件事就是测量它们是否实际上是导致速度下降的原因! (在遇到性能瓶颈时,很容易使自己迷惑)

我最后的建议是使用管理您的依赖关系的构建系统。我个人使用Nix,但是也听到了有关Guix的好消息。还有诸如Docker之类的替代品,从科学的角度来看,它们的用处远没有那么多,但也许更熟悉了。可以像您描述的那样“丢弃”代码,但是它们对于科学计算的可重复性的好处是巨大的。考虑运行这样的Shell脚本(例如run.sh):

#!/usr/bin/env bash
set -e
make all
./analyse < ./dataset > output.csv


我们可以将其重写为Nix“派生”,例如这样(例如run.nix ):

with import <nixpkgs> {};
runCommand "output.csv" {} ''
  cp -a ${./.} src
  cd src
  make all
  ./analyse < ./dataset > $out
''


''...''之间的内容是bash代码,与之前相同,只是${...}可用于“拼接”其他字符串的内容(在本例中为./.,它将扩展到包含run.nix的目录的路径)。 with import ...行导入了Nix的标准库,该库为运行bash代码提供了runCommand。我们可以使用nix-build run.nix进行实验,这将给出类似/nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv的路径。

这对我们有什么帮助? Nix将自动设置一个“干净”的环境,该环境只能访问我们明确要求的内容。特别是它无法访问诸如$HOME之类的变量或我们已安装的任何系统软件。这使结果独立于我们当前计算机的详细信息,例如~/.config的内容或我们刚安装的程序的版本;也就是阻止他人复制我们的结果的东西!这就是为什么我添加了cp命令的原因,因为默认情况下将无法访问该项目。 Nix脚本无法使用该系统的软件似乎很烦人,但是它却是相反的:我们不需要在系统上安装任何东西(Nix除外)就可以在脚本中使用它。我们只是要求它,Nix就会消失,并且需要获取/编译/进行任何必要的操作(大多数内容将作为二进制文件下载;标准库也很大!)。例如,如果我们要一堆特定的Python和Haskell软件包,用于这些语言的某些特定版本,再加上其他一些垃圾(因为为什么不这样?):

with import <nixpkgs> {};
runCommand "output.csv"
  {
    buildInputs = [
      gcc49 libjson zlib
      haskell.packages.ghc802.pandoc
      (python34.withPackages (pyPkgs: [
        pyPkgs.beautifulsoup4 pyPkgs.numpy pyPkgs.scipy
        pyPkgs.tensorflowWithoutCuda
      ]))
    ];
  }
  ''
    cp -a ${./.} src
    cd src
    make all
    ./analyse < ./dataset > $out
  ''


相同的nix-build run.nix将运行此命令,获取我们首先请求的所有内容(并缓存所有内容,以备日后使用)。输出(任何名为$out的文件/目录)将由Nix存储,这是它发出的路径。它由我们要求的所有输入(脚本内容,其他程序包,名称,编译器标志等)的加密哈希标识;那些其他软件包通过其输入的哈希值来标识,依此类推,这样我们就可以对所有内容都有完整的了解,可以追溯到编译了bash的GCC版本的GCC版本,等等! >
希望我已经证明它为科学代码买了很多东西,而且相当容易上手。它也开始受到科学家的重视,例如(在Google上排名最高)https://dl.acm.org/citation.cfm?id=2830172因此,这可能是一种有价值的技能来培养(就像编程一样)

评论


非常详细,有用的答案-我真的很喜欢其他答案,但是断言听起来像是非常有用的第一步。

–希瑟
18年7月6日在19:15

#10 楼

如果不考虑全面的版本控制+包装+单元测试的思维方式(这是您应该在某个时候尝试实现的良好编程习惯),我认为适合的一种中间解决方案是使用Jupiter Notebook。这似乎可以更好地与科学计算集成。

它的优点是您可以将思想与代码混合;解释为什么一种方法比另一种更好,并且在特设部分中保留了旧代码。除了正确使用单元格之外,自然会导致您将代码片段化并将其组织为有助于理解的功能。

评论


另外,这确实有助于提高可重复性-例如,您可以运行完全相同的代码来生成发布图,或者返回到几个月前收起的内容,也许以纳入审阅者的评论。

– afaulconbridge
18年7月6日在15:53

对于想要阅读更多内容的人,这也称为识字编程。

–llrs
18年7月10日在14:32

#11 楼

最佳答案已经很好,但是我想直接解决您的一些问题。


编写较小的代码是否需要单元测试?


代码的大小与单元测试的需求没有直接关系。它是间接相关的:单元测试在复杂的代码库中更有价值,而小型代码库通常不像大型代码库那么复杂。

单元测试在容易出错的地方或在您遇到错误的时候照亮了代码。将会有很多此代码的实现。单元测试对您当前的开发没有多大帮助,但是它们却可以防止您将来犯下导致现有代码突然不当行为的错误(即使您没有碰到那件事)。 >假设您有一个应用程序,其中库A执行数字平方,而库B应用勾股定理。显然,B依赖于A。您需要修复库A中的某些内容,并且说您引入了一个错误,该错误可以对数字求立方而不是对数字求平方。

库B突然开始行为异常,可能引发异常或只是给出错误的输出。当您查看库B的版本历史记录时,您会发现它没有被修改。有问题的最终结果是,您没有任何迹象表明可能出了什么问题,并且您必须在意识到问题出在A之前调试B的行为。这是浪费的精力。

输入单元测试。这些测试确认库A可以正常工作。如果您在库A中引入一个错误,导致其返回不良结果,那么您的单元测试将捕获该错误。因此,您不会试图调试库B。
这超出了您的范围,但是在持续的集成开发中,只要有人提交一些代码,就执行单元测试,这意味着您会知道自己已经破坏了某些东西尽快。

特别是对于复杂的数学运算,单元测试可能是一件幸事。您进行一些示例计算,然后编写单元测试,该单元测试将计算的输出与实际的输出进行比较(基于相同的输入参数)。

但是,请注意,单元测试不会帮助您创建良好的代码,而只会维护它。如果您通常只编写一次代码而从不重新使用它,那么单元测试的好处就会减少。


OOP怎么样?


OOP是一种思考不同实体的方法,例如:


Customer要购买Product时,他与Vendor接收Order。然后,Accountant将支付Vendor


将其与函数式程序员的想法进行比较:


当客户想要purchaseProduct()时,他会talktoVendor(),所以他们会sendOrder()给他。然后,会计将payVendor()


苹果和橙子。从客观上讲,它们都不比另一个更好。需要注意的一件有趣事情是,对于OOP,Vendor曾被提及两次,但它指的是同一件事。但是,对于函数式编程,talktoVendor()payVendor()是两个独立的东西。
这展示了两种方法之间的区别。如果这两个操作之间有很多共享的特定于供应商的逻辑,则OOP有助于减少代码重复。但是,如果两者之间没有共享的逻辑,则将它们合并到一个Vendor中是徒劳的工作(因此,功能编程会更有效)。

通常,数学和科学计算都是不依赖于隐式共享逻辑/公式的不同操作。因此,函数式编程比OOP更为常用。


什么类型的方法在进行“科学编程”时相对于处理较大的代码而言,可以快速地编写良好,清晰的代码?项目?


您的问题意味着无论您是进行科学编程还是从事大型(我假设是企业级)项目,“好的,干净的代码”的定义都会改变。

好的代码的定义不会改变。但是,避免复杂性的需要(可以通过编写简洁的代码来完成)确实发生了变化。

这里又回到了同样的论点。


如果您从不重新访问旧代码并完全理解逻辑而又不必将其划分开,那么请不要花费过多的精力使事情可维护。
如果您确实要重新访问旧代码,或者所需的逻辑太复杂而无法一次解决所有问题(因此需要对解决方案进行划分),那么请专注于编写干净,可重用的close。


我问这些问题是因为编程本身通常并不复杂。它更多地是关于我正在使用程序进行测试或研究的数学或科学。


我在这里与众不同,但是当您回顾现有代码时,您同时在看数学和编程。如果是人为的或复杂的,那么您将很难阅读它。


例如,当两个变量和一个函数可能会照顾到它时,是否需要一个类?除了OOP原则,我编写类来容纳一些数据值的主要原因是因为它简化了方法参数的声明和返回值。例如,如果我有很多使用位置(纬度/经度对)的方法,那么我很快就会不得不输入float latitude, float longitude,并且会更喜欢编写Location loc

当您考虑方法通常返回一个值(除非存在特定于语言的功能以返回更多值),而类似位置的事情希望您返回两个值(lat + lon)时,这又变得更加复杂。这激励您创建一个Location类来简化您的代码。


例如,当两个变量和一个函数可能会照顾到它时,是否需要一个类?


另一个需要注意的有趣事情是,您可以在不混合数据值和方法的情况下使用OOP。并非每个开发人员在这里都同意(有人称其为反模式),但是您可以拥有贫乏的数据模型,其中具有单独的数据类(存储值字段)和逻辑类(存储方法)。
当然,频谱。您不需要完全贫血,您可以在认为适当时使用它。

例如,仅将一个人的名字和姓氏连接在一起的方法仍然可以包含在Person类本身中,因为它不是真正的“逻辑”而是计算值。 >

(考虑到通常情况下,程序的速度也应优先考虑更快的一端-当您运行25,000,000+个仿真时间步长时,您有点希望它成为现实。)


类总是和其字段之和一样大。再次以包含两个Location值的float为例,这里需要注意的是,单个Location对象将占用两个单独的float值一样多的内存。

从这个意义上讲,它不会不管您是否使用OOP。内存占用量是相同的。

性能本身并不是一个很大的障碍。两者之间的区别使用全局方法或类方法与运行时性能无关,但与编译时生成字节码有关。

这样想:无论我用英语还是西班牙语写蛋糕食谱,都不会改变蛋糕需要30分钟烘烤的事实(=运行时性能)。食谱语言唯一改变的是厨师如何混合配料(=编译字节码)。

特别是对于Python,您不需要在调用之前显式地预编译代码。但是,当您不进行预编译时,将在尝试执行代码时进行编译。当我说“运行时”时,我指的是执行本身,而不是执行之前的编译。

#12 楼

干净的科学代码的好处



...看编程书籍,它们似乎常常在大型项目中得到解决。


...如果可以完成一些标准的工作,编写写为OOP的代码真的有意义,由于程序的简短,编写代码的速度会快得多,并且具有相似的可读性吗?



从将来的编码器的角度考虑您的代码可能会有所帮助。

为什么他们打开此文件?
他们正在寻找什么?

根据我的经验,
干净的代码应该使验证结果变得容易


使用户容易准确了解他们运行程序所需的操作。


您可能需要对程序进行划分,以便可以分别对各个算法进行基准测试。


避免编写功能带有反直觉的副作用,因为一个不相关的操作会导致另一种操作操作会有所不同。如果无法避免,请记录您的代码需要什么以及如何进行设置。


干净的代码可以用作将来的代码示例代码
清除注释(包括表示应该如何调用函数的函数)和功能完全分离的函数,对于刚开始(或将来对您)有用的东西使您的工作有用的时间可能会产生巨大的差异。
除此之外,为您的算法创建一个真正的“ API”可以使您更好地准备,如果您决定将脚本放入一个真正的库中供其他人使用。
建议
使用注释引用数学公式。

在“引用”数学公式中添加注释,尤其是在您使用优化(触发恒等式,泰勒级数等)的情况下。
如果您从书中获得了该公式,请添加注释,说明John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180,如果您在网站或纸上找到了该公式,也请引用。
我建议避免使用“仅链接”注释,请确保您在某处通过名称引用该方法以允许人们使用它,我遇到了一些“仅链接”注释,这些注释已重定向到旧的内部页面,它们可能会令人沮丧。
如果仍然容易以Unicode / ASCII格式阅读,则可以尝试在注释中键入公式,但这会很尴尬(代码注释不是LaTeX)。

明智地使用注释
如果您可以使用良好的变量名/函数名来提高代码的可读性,请首先这样做。请记住,注释会一直保留下去,直到将其删除为止,因此请尝试做出不会过时的注释。
使用描述性变量名

单字母变量可能是最佳选择如果它们是公式的一部分。
对于将来的读者来说,能够查看您编写的代码并将其与正在实现的方程式进行比较可能至关重要。
适当时,请考虑添加一个后缀描述它的真实含义,例如。 xBar_AverageVelocity

如前所述,我建议您在某处的注释中清楚地指出您要使用的公式/方法。

编写代码以使程序针对已知的已知方法运行错误的数据。

编写较小的代码段是否需要单元测试?

我认为单元测试可能会有所帮助,我认为科学代码的最佳单元测试形式是在已知的坏数据和好数据上进行了一系列测试。
编写一些代码以运行算法,并检查结果与预期的差异程度。这将帮助您发现(可能非常糟糕且难以发现)问题,在这些问题中您不小心对导致错误的肯定结果的代码进行了硬编码,或者犯了使函数始终返回相同值的错误。
注意,这可以在任何抽象级别上完成。例如,您可以测试整个模式匹配算法,也可以测试在优化过程中仅计算两个结果之间距离的函数。首先从对您的结果最关键的领域和/或您最关心的代码部分开始。
使添加新测试用例变得容易,考虑添加“辅助”功能,并有效地构建输入数据。这可能意味着可能将输入数据保存到文件中,以便您可以轻松地重新运行测试,尽管要格外小心,以免出现误报或偏差/琐碎的测试用例。
考虑使用交叉验证之类的方法,请参阅交叉验证以获取更多信息。
使用版本控制
我建议使用版本控制并将您的存储库托管在外部站点上。有些站点可以免费托管存储库。
优势:

它提供了备份功能,以防您的硬盘出现故障
它提供了历史记录,使您不必担心是否存在存储库。最近出现的问题是由您不小心更改文件引起的,还有其他好处。
它允许您使用分支,这是处理长期/实验代码而不影响无关工作的好方法。

在复制/粘贴代码时要格外小心

我的代码风格很复杂,并且充满了过时的注释,指出了执行某项操作的替代方法或复制了几行代码。



复制/粘贴代码可以节省您的时间,但这是您可以执行的最危险的操作之一,尤其是如果代码不是您自己编写的(例如,如果代码是(来自同事)。


一旦代码开始工作并经过测试,我建议您仔细检查一下它,以重命名任何变量或注释掉任何您未使用的内容支架。



评论


另请参阅:如何为难以预测结果的代码编写单元测试?

– jrh
18-10-9在12:17



#13 楼

通常发明该行业的工具来解决需求。如果您需要使用该工具,则可以不用。

特别地,科学程序不是最终目标,而是手段。您编写该程序来解决您现在遇到的问题-您不希望该程序在十年内被其他人使用(并且必须维护)。仅此一项就意味着您不必考虑任何允许当前开发人员记录其他人的历史记录的工具,例如版本控制,或捕获诸如单元测试之类的代码中的功能。

有什么好处?那您呢?


版本控制很好,因为它使您可以非常轻松地备份工作。截至2018年,github是一个非常受欢迎的地方(您可以随时在需要时将其移动-git非常灵活)。备份的一种便宜而简单的替代方法是操作系统中的自动备份过程(适用于Mac的Time Machine,适用于Linux的rsync等)。您的代码需要放在多个地方!
单元测试是不错的,因为如果您首先编写它们,则必须考虑如何检查代码的实际作用,这有助于您为代码设计更有用的API。如果您打算编写代码以便以后重用,并且在更改算法时会有所帮助,这将很有帮助,因为您知道它可以在这些情况下使用。
文档。学习用您使用的编程语言编写适当的文档(例如Javadoc的Javadoc)。为未来写信。在此过程中,您会发现好的变量名使编写文档变得更加容易。重复。与诗人一样,对您的文档给予同等的照顾。
使用好的工具。查找可以帮助您并很好地学习它的IDE。像将变量重命名为更好的名称这样的重构要容易得多。
如果您有同行,请考虑使用同行评审。让局外人查看并理解您的代码是您编写的未来的当下版本。如果您的同龄人不了解您的代码,则以后可能也不会。


评论


这个答案怎么没有收到赞?现在。我们的小组发现同行评审是所有工具中最有效的工具之一,对于科学代码,它比单元测试重要得多。将科学期刊文章中的一组复杂方程式转换为代码时,很容易出错。科学家和工程师常常造就极其贫穷的程序员。同行评审会发现架构上的丑陋之处,使代码难以维护/理解/使用。

–David Hammen
18年7月8日在14:53

#14 楼

除了这里已经提供的好的建议之外,您可能还需要考虑编程的目的,以及因此对您而言重要的内容。

”“这与我正在测试的数学或科学有关,通过编程进行研究。“

如果目的是为了进行试验和测试以达到您自己的理解,并且您知道结果是什么,那么您的代码基本上可以快速投入使用,并且当前的方法可能足够,尽管可以改进。如果结果与预期不符,您可以返回并进行审查。

但是,如果编码的结果告诉了您研究的方向并且您不知道结果应该是什么,那么正确性就变得尤为重要。代码中的错误可能会导致您从实验中得出错误的结论,对您的整体研究产生各种不良影响。

在这种情况下,将代码分解为带有单元测试的易于理解和可验证的功能将为您提供更多坚固的建筑用砖,使您对结果有更大的信心,并可能使您免于日后大失所望。

#15 楼

与版本控制和单元测试一样,它们可以使整体代码井井有条,功能正常,实际上都无法帮助您编写更简洁的代码。


版本控制将使您了解代码的方式和时间
杂乱无章。
尽管代码
是一团糟,但是单元测试可以确保它仍然可以正常工作。

如果您想要阻止自己编写混乱的代码,您需要一个在混乱发生的地方工作的工具:编写代码时。一种流行的工具称为棉短绒。我不是python开发人员,但看起来Pylint可能是一个不错的选择。

linter会查看您编写的代码,并将其与一组可配置的最佳实践进行比较。如果linter有一个变量必须为camelCase的规则,并且您在snake_case中写了一个,它将标记为错误。优良的短子有规则,范围从“必须使用声明的变量”到“函数的循环复杂度必须小于3”。

大多数代码编辑器都可以配置为每次保存时都运行短子,或仅在您键入时输入,并内联指出问题。如果键入类似x = 7的内容,则x将突出显示,并带有使用更长更好名称的说明(如果这是您配置的名称)。这就像大多数文字处理程序中的拼写检查一样,使其难以忽略,并有助于养成更好的习惯。

评论


这应该有更多的支持。 +1

–希瑟
18年7月10日在15:11

但是,为了天堂的缘故,请确保您知道如何将短绒配置成您喜欢的样式,否则它将使您大为恼火。

– DrMcCleod
18年7月11日在13:17

#16 楼

您列出的所有内容都是隐喻工具箱中的工具。就像生活中的任何事物一样,不同的工具适合于不同的任务。

与其他工程领域相比,软件可以处理很多独立的组件,而这些组件本身非常简单。分配声明不会根据房间的温度波动而做出不同的评估。 if语句未腐蚀到位,并且过一会儿仍返回相同的内容。但是,由于单个元素是如此简单,并且是由人类编写的软件,因此这些元素被组合成越来越大的部分,直到结果变得如此庞大和复杂,以至于人们无法进行精神上的管理。

随着软件项目的发展,人们已经对其进行了研究并创建了工具来尝试管理这种复杂性。 OOP就是一个例子。越来越多的抽象编程语言是另一种方法。由于软件中的大量资金正在做更多的事情,因此您将看到和了解的实现这些目的的工具。但是看来这些情况并不适合您。

因此,您不必觉得自己需要做任何事情。归根结底,代码只是达到目的的一种手段。不幸的是,最好的方法是对一些较大的项目进行正确和正确的选择,因为当您想到工具箱时,很难知道缺少了什么。

无论如何,只要您的脚本很小,我都不会担心不使用OOP或其他技术。您描述的许多问题只是一般的专业组织技能,即不丢失旧文件是所有领域都必须处理的事情。

#17 楼

除了到目前为止提供的所有好的建议之外,我随着时间的推移学会了一种发现并且很重要的实践,那就是非常自由地在代码中添加详细的注释。对我来说,很长一段时间过去后,这对我来说是最重要的一件事。向自己解释自己的想法。这需要花一些时间,但是相对来说却很容易,而且几乎没有痛苦。

我有时候的注释行是代码的两倍或三倍,特别是当这些概念或技术对于我,请自己向自己解释。

进行版本控制,改进您的做法等。...以上所有这些。但是,随身携带一些东西给自己解释。真的很好用。

#18 楼



它的易于维护或发展可能无关紧要,因为这种可能性不会发生。

它的效率可能并不重要。

它是否具有出色的用户界面或是否可以抵御恶意攻击者可能都没有关系。

它可读性可能很重要:读取您的代码的人可以轻松地使自己相信它已完成了它所声称的工作。

当然很重要,这是正确的。如果程序给出的结果不正确,那就是您窗外的科学结论。但是它只需要正确处理您实际上要处理的输入即可;如果给定负的输入数据值,如果所有数据值都是正值,它是否倒退并不重要。

保持一定水平的变更控制也很重要。您的科学结果必须具有可重复性,这意味着您需要知道程序的哪个版本产生了要发布的结果。因为只有一名开发人员,所以变更控制不需要非常复杂,但是您需要确保可以回到某个时间点并重现结果。

所以不要不必担心编程范例,面向对象,算法优美。不必担心其清晰度和可读性,以及随着时间推移所做更改的可追溯性。不用担心用户界面。不必担心测试输入参数的每种可能组合,但要进行足够的测试以确信(并使其他人确信)您的结果和结论有效。

#19 楼

我曾在与类似的环境中一起工作过,他们编写了很多(数学/科学)代码,但是由于您所描述的相同原因,他们的进度很慢。但是,我注意到一件事情进展顺利,我认为这也可以对您有所帮助:建立并维护一组专用库,这些库可以在多个项目中使用。这些库应提供实用程序功能,因此将有助于使您当前的项目保持特定于问题领域。例如,您可能必须在自己的领域中处理许多坐标转换(ECEF, NED,纬度/经度,WGS84等),因此应将类似convert_ecef_to_ned()的函数输入到名为CoordinateTransformations的新项目中。将项目置于版本控制下,并将其托管在部门的服务器上,以便其他人可以使用(并希望对其进行改进)。
几年后,您应该拥有功能强大的库集合,并且您的项目仅包含特定于代码的代码特定问题/研究领域。

一些更一般的建议:


始终旨在尽可能准确地建模特定问题
,没有不管是什么这样,软件设计
问题,例如在变量的什么位置/何处/如何放置
就变得更加显而易见。
由于科学代码,我不会为测试驱动开发而烦恼描述想法和概念,
更具创造力和动感;没有定义的API,要维护的服务,更改功能时他人代码的风险等。


评论


不要让其他人改进它。可能是他们不了解代码的目的,只会把事情搞砸了。

–宏线程
18年7月8日在7:38

@mathreadler好吧,如果它们是通用实用程序库,那么其他人很难搞清楚,这就是想法。

–吉格酥
18年7月8日在12:04

为什么很难弄乱通用库?如果您不知道自己在做什么,或者如果您真的很努力,那么事情就不那么困难了。

–宏线程
18年7月8日在12:06



@mathreadler因为例如通常只有一种方法可以进行坐标转换或单位转换。

–吉格酥
18年7月8日在12:41

通常有很多方法,具体取决于数字在内存中的存储方式,它们使用的表示形式以及许多其他内容,打算为该库编译的CPU。例如,一个编码器可能假定每个人都将始终使用IEEE双精度,但是另一种编码器几乎总是使用单精度或其他三分之一奇怪的格式。然后,一个编码器将使用模板多态性,但另一个编码器可能对此模板过敏,大约三分之一甚至更怪异的一个将对低级c或汇编语言中的所有内容进行硬编码。

–宏线程
18年7月8日在12:47

#20 楼

以下是我的观点,并受到我自己的特定道路的很大影响。

编码通常带来教条式的观点,说明您应该如何做事情。我认为您需要查看累积的价值和成本来确定适当的策略,而不是技巧和工具。

编写好的,可读性,可调试的,可靠的代码需要花费大量精力。时间和精力。在许多情况下,由于规划范围有限,因此这样做(分析瘫痪)是不值得的。

一个同事有一个经验法则。如果您第三次要做的基本上是同一类事情,则要花点力气,否则,快速而肮脏的工作是适当的。

进行某种类型的测试是必不可少的,但是对于一个关闭的项目,只需盯着眼球
就足够了。对于任何实质性的事情,测试和测试基础结构都是必不可少的。这样做的好处是可以使您在编码时解放出来,代价是如果测试
专注于特定的实现,那么测试也需要维护。测试
也会提醒您事情是如何运作的。

对于我自己的一个脚本(通常用于验证概率等的估计之类的事情) ,我发现两个非常有用的小事情:1.包括
注释,说明如何使用代码。 2.包含有关为什么编写代码的简短描述。当您编写代码时,这些事情非常明显,但是显而易见,这会浪费时间:-)。

OOP是关于重用代码,抽象,封装,分解等的。非常有用,但是
如果不是最终的目标,那么容易迷失方向。
生产高质量的产品需要花费时间和精力。

#21 楼

虽然我认为单元测试有其优点,但它们对科学发展的价值却令人怀疑-它们通常太小而无法提供大量价值。

但是我真的很喜欢科学代码的集成测试:

隔离一小段代码,这些代码可以自己工作,例如ETL管道。然后编写一个提供数据的测试,运行etl管道(或仅执行一个步骤),然后测试结果是否符合您的期望。
尽管被测试的块可以包含很多代码,但该测试仍提供了价值:


您有一个方便的钩子可以重新执行代码,这有助于经常运行它。
您可以在测试中测试一些假设
如果某些想法破了,添加失败的测试并进行修复很容易
将所需的输入/输出进行编码,从而避免了由于试图猜测输入数据格式而引起的常见麻烦。
虽然不如单元测试那么精简。 ,IT测试仍然可以帮助您将代码分开,并迫使您在代码中添加一些界限。

我经常使用这种技术,并且通常以相对易读的主要功能结尾,但是功能通常相当长且难看,但是由于强大的I / O边界,可以对其进行快速修改和重新安排。

#22 楼

我通常在非常庞大的源代码基础上工作。我们使用您提到的所有工具。最近,我开始为副项目开发一些python脚本。它们最多只有几十到几百行。出于习惯,我将脚本提交给了源代码管理。这很有用,因为我可以创建分支来尝试可能不起作用的实验。如果需要复制代码并出于其他目的对其进行修改,我可以进行分叉。如果我需要再次将其拿出来,这将保留原样。

对于“单元测试”,我只有一些输入文件,这些文件旨在产生一些我手工检查的已知输出。我可能可以使它自动化,但感觉这样做要比花时间节省更多的时间。这可能取决于我必须多久修改和运行一次脚本。无论哪种方式,如果可行,都可以做到。如果麻烦多于其价值,请不要浪费时间。

#23 楼

在编写代码时(就像在一般情况下一样),主要问题是:


您要考虑的读者是谁?还是谁在使用您的代码?


当您是唯一的受众时,像正式的编码准则之类的事情就没有意义。

另一方面,以某种方式编写代码会很有帮助,您将来可以立即理解它。

“好样式”将是其中一种,对您有最大的帮助。这种风格应该是我无法给出的答案。

我认为对于150 LOC的文件,您不需要OOP或单元测试。当您有一些不断发展的代码时,专用的VCS将很有趣。否则,用.bak可以解决问题。这些工具可以治愈疾病,您甚至可能没有。

也许您应该以这种方式编写代码,即使您在醉酒时阅读代码,也能够阅读,理解和修改它。