身为程序员,除了追求代码的正确性、健壮性之外,还需考虑代码的美观性。优雅的代码让人赏心悦目,而糟糕的代码让人直呼辣眼睛,恨不得把写代码的人拉过来当场质问,你为什么要这么写?!如果不关注代码的优雅性,还可能造成更严重的后果------屎山。这是一个相当有味道的词,足以见得人们对劣质代码的厌恶。
优雅的代码不仅是为了别人的身心健康,更是避免将来重看自己代码时还能够看得懂。
什么样的代码算是优雅的代码
优雅的代码是一种感觉,让人看了就觉得舒服 .....咳咳,打住。开个玩笑,优雅的代码当然不是一个玄学的问题。我们可以从一下几个维度来评估代码的优雅性。
-
可读性:优雅的代码应当易于理解。它应当像故事一样能够流畅地阅读,使得其他开发者很容易就能理解它的功能和实现方式。
-
简洁性:优雅的代码应当避免不必要的复杂性。这意味着它避免了冗余的代码,并且尽可能的精简。
-
明确性:优雅的代码明确地表达了其意图。变量和函数的命名应当清晰明确,避免使用模糊不清或者有歧义的命名。
-
模块化和可重用性:优雅的代码能够很好地进行模块化,同时也提供了高度的可重用性。这意味着代码被组织成可独立运行且可轻松移植和复用的模块或组件。
-
健壮性和可扩展性:优雅的代码在面对异常情况时能够进行恰当的处理,并且具有良好的可扩展性,可以在不对现有代码进行大规模修改的情况下添加新的功能。
-
符合编程约定或风格指南:每种编程语言或者项目通常都会有一套编程约定或者风格指南,优雅的代码应当遵循这些约定或指南,这不仅可以增强代码的可读性,还可以使得代码在整个项目中风格统一。
-
注释:虽然优雅的代码本身应当尽可能地自解释,但是在必要的地方添加适当的注释仍然是非常重要的。好的注释可以帮助读者理解代码的功能和实现方式,以及在特定地方为什么要采用某种设计或者策略。
其中的每一个点都可以展开细讲,具体可见罗伯特马丁先生所著的《代码简洁之道》(《Clean Code》)
2年前我看过这本书,现在还记得里面讲的一些规则,并沿用至今。
例如:
- 一个函数只做一件小事
- 命名要要表达足够的含义,哪怕长一点也没关系
- 不要写冗余的注释,冗余的注释和糟糕的代码一样浪费人的时间
此外还可参考各大厂的编程规范指南。例如:阿里巴巴编码规范(Java)、google-styleguide
如何写出漂亮、优雅的代码
代码的优雅性涉及方方面面,不是一朝一夕能够做到的,有些方面甚至需要有足够的经验之后才能做到。例如可重用性、可扩展性。这要求设计程序时有更多的考量。
但是有一点是敲代码的第一天就要开始注意的,那就是简洁性。而现在,即便是刚接触编程的新手也能很好的解决这个问题。------想必大家已经猜到了,那就是借助大语言模型。
事实上对于一个刚开始接触编程或者接触不久的同学来讲,ta并不清楚什么是好代码或者坏代码。直到ta看到别人写的好代码,并开始刻意模仿,才能逐步的改善自己的代码。其实这就是写出优雅代码的两个最关键因素了:看 、模仿。但还有一个细节,先卖个关子。
这就好像高中学习写作文,通过阅读优秀的作文,然后去模仿,久而久之就能写出好作文了。但,真的是这样吗?我记得我高中也看了不少好的作文,为什么我还是写得稀烂。现在换个方式,我辛辛苦苦写一篇稀烂(40分)的作文,然后交给老师,老师不改动我的框架,只是通过删除和修改句子,写一篇55分的作文给我,并告诉我为什么要这么改动,然后我再去模仿,就这么一直重复训练,我相信我一定能写出好的作文。也许你会说如果框架从一开始就很糟糕,内部的细节再好也不行,那是框架层面问题了,同样可以找大量同类型的作文来模仿。直到有一天不管是框架还是细节都能信手拈来,自然能写好一篇作文。
想必大家已经明白我刚才说的那个细节是什么了,那就是:有针对的模仿,写细节的时候就模仿细节,写框架的时候就模仿框架,这样的进步才是最快的。
这个问题最难的地方就是首先你能找到一个老师,ta来帮你修改,告诉你什么是好的。现在大语言模型就是这个老师。把你写好的代码丢给它,让它帮你改,改完之后再模仿。
具体演示
经常听一些老程序员吐槽所谓的if else垃圾代码,每一个程序员在刚开始编程时不可避免的也写过很多这样的代码。虽说人们吐槽它不好,但很少听到人们解释它为什么不好。
事实上if else是代码中不可或缺的逻辑部分,没有任何一个程序能够缺少条件语句。而糟糕的代码往往含有大量、零散的if else。这样的代码最大的问题在于:
if是某个特定的情况下才执行,而大量零散的if else就意味着将代码的逻辑打散了(如果对这样的代码画一个流程图就更显然了)。而每个人思考问题的逻辑是不一样的,让别人跟着零散的逻辑走是一件很痛苦的事。
为此我找来了以前的代码作为演示:
第一题:
我以前的代码如下:
python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp1 = [1] * n
first_obstacle = -1
# 单独处理第一行
for idx, num in enumerate(obstacleGrid[0]):
if num == 1:
first_obstacle = idx
break
if first_obstacle != -1:
for col in range(first_obstacle, n):
dp1[col] = 0
for row in range(1, m):
# 处理第一列
dp2 = []
if obstacleGrid[row][0] == 1:
dp2.append(0)
else:
dp2.append(dp1[0])
for col in range(1, n):
if obstacleGrid[row][col] == 1:
dp2.append(0)
else:
dp2.append(dp2[-1] + dp1[col])
dp1 = dp2.copy()
return dp1[n-1]
上面的代码已经优化了空间复杂度,将二维dp压缩为一维dp,是一个正确的算法。
从逻辑上来讲:
-
先单独处理了第一行形成dp1
-
在循环内部又单独处理第一列
来看看chatGPT的优化:
- 进一步优化空间
- 统一所有行与列的操作,不用再单独处理第一行第一列
正如我前面所说,这里减少了if else来处理特殊情况,毫无疑问要比我之前的代码优雅。
第二题:
以前的代码:(注:此题可采用KMP算法获得更低的时间复杂度,这不是这里的重点)
python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
i = 0
while i < len(haystack):
if haystack[i] == needle[0]:
j, k = 0, i
while j < len(needle) and k < len(haystack):
if haystack[k] != needle[j] :
break
k += 1
j += 1
if j == len(needle):
return i
i += 1
return -1
chatGPT的优化:
这里已经保证haystack[i + j]不会越界,所以可以进一步优化为
python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
for i in range(len(haystack) - len(needle) + 1):
if all(haystack[i + j] == needle[j] for j in range(len(needle))):
return i
return -1
- 将原本的三个指针优化为两个
- 采用all语法看起来更简洁
模仿
有了例1的纠正,下面一道题就可以进行模仿
python
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0]) if grid else 0
dp = [0] * n
for row in range(m):
for col in range(n):
if col == 0:
dp[col] += grid[row][col]
elif row == 0:
dp[col] = dp[col - 1] + grid[row][col]
else:
dp[col] = grid[row][col] + min(dp[col-1], dp[col])
return dp[n-1]
再回头来看看我以前的代码:
总结
这篇博客重点讲解如果保证函数内部代码的优雅性,这是一个细粒度的层面,但是同样非常重要。
给出prompt:
请对下面的代码进行优化,以提高代码的可读性和简洁性,并确保算法的正确性:
code ...
请修改以上代码,确保代码的逻辑和功能不变,并优化其结构、命名和注释等方面以提高代码的可读性和简洁性。
最后,语言模型的优化并不能保证一定正确,有时候还会产生负优化,大家要谨慎采用。
与君共勉。