屎山代码养成记

 前言

作为程序猿/媛,代码的优雅、简洁、易懂、可复用等是大家追求的目标;但平时都沉浸在业务需求的开发中,很容易就会因为产品需求迭代的快速上线要求,让我们临时抛弃了对优秀代码的追求。最近一年由于业务萎缩,几乎所有需求都是对存量业务的迭代和优化,在这里面发现了不少因为代码设计或是仅为满足当时需求而写的代码,后期再修改十分困难。之前已经被约过一次稿了(传送门),这次再整理下并且更新一波。

1. 通用问题

1.1 不要硬编码

image.png

对于业务开发来说,上图中的兜底参数大概率不会被复用,但这样硬编码并不是一个好习惯,而且也没有注释来说明兜底的值的具体含义。

我们的项目里还有很多类似的场景,例如 icon、图片链接等,都是直接一个链接就塞在业务逻辑里了。这种类型的资源复用的概率就比较高了。如果不是用常量统一维护的话,可能出现同一个资源被不同的人上传多次导致对应多个链接;或者需要修改时要改多个地方。

这些都还是比较好理解的,毕竟知道它是个房间 id,或者是个图片链接。在我们项目中,有一些产品需要的数据上报,这些上报的值有相当一部分是纯数字,例如:int1: 1/2/3,这种硬编码如果没有注释根本不知道什么意思,只能找到对应的上报文档看解释。

所以最佳的方式还是统一用常量维护,新建一个文件而已,问题不大。

BTW,也发现不同的人对常量的理解不同,例如上图的strRoomId,虽然从 url 链接上拿到之后它就不会变了,但是对于不同的房间来说,它的值还是不同的,所以它还是变量。常量应该是在程序编译之后,无论在任何场景下都不会改变的值。

1.2 控制文件大小

image.png

在一些技术群里聊过之后发现,1k+行的文件其实不算啥,但对于脑容量实在不够用的我来说,1k+行的文件看起来真的头疼,经常跳着跳着就不知道在哪了,而且大文件的另一个问题就是里面有太多的函数和超长的模板;过多的函数可能还是互相调用的,超长的模板可能里面揉了太多的三元选择逻辑。对于大型前端项目来说真的是灾难。

因此在做 Code Review 时,一定要将文件大小控制在一定的行数之内,这个数字不同的团队可能有不同的标准,对我来说,我最大的忍受行数是 800 行,超过这个数字的,一定有可以优化的空间,不是模板太长就是逻辑太多,任何一个都可以做优化。现在在项目中我给自己的红线是:文件大小尽量保持在400行内,模板代码尽量保持在100行内,模板代码中尽量少写js逻辑。

作为有追求的程序猿/媛嘛,一定要学会控制自己,不要随意地放飞自我,否则等待自己的一定是无法翻越的一座又一座“大山”。不要撸码一时爽,重构火葬场

1.3 万恶的嵌套三元选择符

image.png

这个真的无力吐槽了,我觉得正常人类是无法理解的。这还只是简单的接口返回的一些判断,对应的是普通的文案,如果嵌套的三元选择的结果是不同的模板,模板里又有各种三元选择符,那只能说撸码一时爽,读码火葬场

1.4 代码复用

image.png

image.png

我相信这段代码最开始这样写是有它的道理的,但这一定是没有经过思考的一段代码,抛开模板没有复用,至少样式上应该是可以复用的,这里的需求也比较简单,就是有最多三个头像并排放置,可能在多个头像时,样式上会有一些差异,但无论如何,直接写出三种场景的情况是最无法接受的。如果需求变更需要展示最多四个或更多头像,岂不是还得再写更多的场景代码?真是撸码一时爽,迭代火葬场

1.5 抽离公共组件时也要考虑样式的维护

这个例子的截图有点敏感,就直接文字描述好了。

我们的代码仓库是 monorepo 的形式,仓库下有 N 个子项目。之前在 A 项目中有一个组件,后来发现在 B 项目中可以用,就把模板和相关逻辑抽出来了,但是样式没有抽出来。而我们之前的开发模式是重构和前端逻辑是分开不同的同学写的,重构同学把一个项目里的所有样式都写在了一个文件里,而这个抽出来的组件在 B 项目中使用的时候,直接把 A 项目的样式文件给引入了。虽然没有具体的对文件大小影响的数据证据,但是多引入的样式文件有 1000 多行,对最终的样式文件大小还是挺有影响的。

对将业务组件抽离成公共组件,需要注意的事项也比较多,之后会专门写一篇文章来讲。

1.6 对 Promise 的理解

image.png

不知道看官们乍一看是否能看出问题。

这里隐藏的坑是对Promise理解的问题,如果红框中的if判断为true了,那么就会用空字符串resolve当前的Promise,但是代码并不会返回,而是会继续向后执行,并且在后续遇到resolve,其执行后也不会改变resolve的值。这就导致当时我在调试的时候,拿到的值一直是空字符串,排除了各种问题之后,最后才看到这一行代码。

而且这行代码还有一个问题,if后的内容没有用花括号包裹。这个点在 Code Review 的规范中也有不同的声音。有些人认为一行就不需要花括号了,也有人认为即便一行也需要花括号。我的习惯是,只有在if条件后直接return,那我接受不加花括号,其它情况哪怕只有一行内容,也需要把花括号加上。当然这个规范就见仁见智了。

另外,如果是有类似提前resolve的情况,要么就明确地把else写上,要么就在resolve之后加上return语句,因为resolve只是状态的改变,不能当return用。

1.7 慎用位运算

 

javascript

复制代码

if ( !(a & 1) || !(a & 2) ) { // do something A... } else { // do something B... }

上述代码是真实业务中的简化版,业务逻辑是:如果a不是1,或者a不是2,执行do something A的逻辑,否则执行do something B的逻辑。但这里有bug,因为当a的值是1时,!(a&1)的结果是false,但!(a&2)的结果是 true,结果导致进入了do something A的逻辑,其实应该进入do something B的逻辑。

首先,位运算让整个if条件看上去比较难理解,因为不止一个位运算,是多个位运算还要做逻辑,并且做完位运算之后需要再取反,导致if条件的逻辑很绕。这里的业务逻辑其实很简单,如果a的值是12,就执行do something B的逻辑,否则执行do something A的逻辑,即:

 

javascript

复制代码

if ( [1, 2].includes(a) ) { // do something B } else { // do something A }

*注:这里的数字硬编码仅是示例,业务代码中用的是常量

1.8 变量命名问题

 

javascript

复制代码

const func = (res) => { // ...some code a().then(res => { let b = res.xx.yy; // ...other code }); };

光看代码本身,没有逻辑问题,不影响机器执行,但却十分影响人类阅读。因为代码被精简,所以不会觉得有什么问题,但实践中,then里的逻辑很多,这里也可能会用到func的参数res里的内容,如果不注意很容易用错。当然,在typescript的加持下,如果读取了不存在的属性会报错,但这并不是优雅的代码应该出现的。当心撸码一时爽,debug 火葬场。

1.9 组件内部逻辑问题

image.png

红框中的代码我第一次看到时,整个人都呆住了,居然用是否传了某个函数来区分样式。很明显这个组件的开发者是没有想清楚组件应该如何组织的,或是在后期迭代时使用了最简单但是最难懂的方式来实现样式改版。只能说撸码一时爽,后人火葬场。

2. React 使用相关

2.1 使用 useMemo、useCallback 时不关注依赖

很基础的问题了,不添加依赖会导致对应的变量或函数不会更新,其实这个也有插件提示。

image.png

所以不要盲目迷恋大厂,很基础的问题也会没注意,有坏味道的代码哪里都有,谁都会写出不优雅的代码,所以我们需要 Code Review 来相互提醒。Anyway,扯远了。

2.2 useEffect 第二个参数必传

image.png

如果不加第二个参数,每次渲染都会执行,也是很基础的问题。也许是有每次渲染都要执行的逻辑,如果是这样,那干脆不要包一层useEffect了。

2.3 当心组件外部的变量

 

javascript

复制代码

const appid = getParams('appid'); export const ComponentA = () => { const func = () => { if (appid) { // do something A... } else { // do something B... } } // some code... }

上述代码对应一个简单的真实业务逻辑,appid对应的详情页是复用的,在A的详情页中可以直接跳转到B的详情页,此时的做法是更新 url 链接,但由于不是刷新页面,appid其实还是之前的,这就会导致某些直接拿appid做判断的逻辑出现异常。所以,定义在组件外部的最好只有常量。否则真是撸码一时爽,排查火葬场。

3. TypeScript 使用相关

3.1 切勿滥用 any

image.png

既然用了TypeScript,就不要再any了吧,这里哪怕不写,也能通过initData的内容推断出类型,强行写个any,后面全凉了。

3.2 变量指定了类型,但无初始值

image.png

示例中的变量其实是个对象,代码逻辑是通过接口返回值再初始化变量,但如果想将对象中的值传给子组件,还得判断对象是否存在,既然都用TypeScript了,把初始值按类型的定义都写上就行了,如果实在字段太多,单独搞个文件维护就行。

3.3 函数参数的类型不要过于随意

有一个页面里有两个函数,内容非常像,大概只有 5% 左右的代码不同,仔细对比之后发现,这两个函数的参数类型完全不同,分别用了两个不同的结构体,只有 5% 不同代码中用到了不同的key,其它内容用到的key都是一样的。

这样有两个问题:

  1. 增加了代码重复率
  2. 逻辑无法扩展

从代码上看,应该是最开始的函数的参数定义了一个大的结构体,后面另一个同学发现不能直接复用,抽公共函数可能有点耗时,就干脆直接复制一份,把参数类型改一下,特定的逻辑处理一下就完工了。结果就是后来我接手时无法扩展,为了可复用性,只能重新梳理逻辑并抽离公共逻辑。

所以,对于参数类型来说,切勿随便定义一个很大的结构体,这里面用不到的字段会影响整个函数的复用。这一点在后面的组件化设计中也很重要。

仅供参考!!!

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Python修仙养成游戏是一种基于Python语言开发的游,玩家可以在游戏中扮演一个修仙者,通过修炼、任务和战斗等方式提升自己的修仙实力。以下是一个简单的Python修仙养成游戏代码示例: ```python import random class Player: def __init__(self, name): self.name = name self.level = 1 self.exp = 0 self.hp = 100 self.mp = 100 def level_up(self): self.level += 1 self.hp += 10 self.mp += 10 def gain_exp(self, exp): self.exp += exp if self.exp >= 100: self.level_up() self.exp = 0 def attack(self, enemy): damage = random.randint(10, 20) enemy.hp -= damage if enemy.hp <= 0: print(f"{enemy.name}被击败了!") self.gain_exp(50) class Enemy: def __init__(self, name, hp): self.name = name self.hp = hp def attack(self, player): damage = random.randint(5, 15) player.hp -= damage if player.hp <= 0: print(f"{player.name}被击败了!") player_name = input("请输入你的角色名:") player = Player(player_name) while True: enemy_name = input("请输入敌人的名字(输入q退出游戏):") if enemy_name == "q": break enemy_hp = random.randint(50, 100) enemy = Enemy(enemy_name, enemy_hp) while True: print(f"{player.name}的血量:{player.hp}") print(f"{enemy.name}的血量:{enemy.hp}") choice = input("请选择行动(1.攻击 2.逃跑):") if choice == "1": player.attack(enemy) if enemy.hp <= 0: break enemy.attack(player) if player.hp <= 0: break elif choice == "2": print("你逃跑了!") break else: print("无效的选择!") print("游戏结束!") ``` 这段代码实现了一个简单的修仙养成游戏,玩家可以输入角色名和敌人名字,然后进行攻击或逃跑的选择。玩家和敌人的血量会根据攻击造成的伤害进行相应的减少,当其中一方血量归零时,游戏结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋の本名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值