编码 10000 个小时后,开发者悟了:“不要急于发布!”
liuian 2025-09-04 11:56 6 浏览
【CSDN 编者按】在软件开发的道路上,时间是最好的老师。
根据“一万小时定律”,要成为某个领域的专家,通常需要大约一万小时的刻意练习。
本文作者身为一名程序员,也经历了一万小时的编程,最终悟出了一个道理:慢即是快,重视架构设计和代码质量,确保每一行代码都经得起时间的考验。
作者 | Sotiris Kourouklis 翻译 | 郑丽媛
出品 | CSDN(ID:CSDNnews)
我已经做了 7 年多的程序员了,在这期间,我在后端、前端和 DevOps 方面参与了无数个项目。尽管如此,我并不认为自己是一名伟大的程序员;有很多人不仅比我聪明,经验也更丰富。不过这些年来我也总结出了一些经验,让我在编程道路上不断进步,并构建出更可靠且易于维护的软件。
“学会放慢速度,反而能让我编码更快、发布更多,并在整体上更加高效。”
这不仅仅是多年的编程经验,也是我从生活中学到的教训:你必须时刻保持缓慢,不要急躁。
软件开发中的头号问题
很多人在开始编程时,总认为伟大的程序员就像会魔法,他们能以一种无人能懂的独特方式去构建应用程序——但这与事实完全不符。如果仔细查看那些伟大程序员的代码,你会发现它们其实非常简单、很好理解。
不是说在开发应用程序时使用快速、炫酷或使用最尖端的技术,你才能被认为是一名优秀的程序员。管理者也常常犯这个错误,他们老是根据一些不切实际的标准来进行招聘。在我看来,要求应聘者从零开始构建一个应用程序的编程测试是一种相当糟糕的评估方法,它无法全面反映一个人的能力。相比之下,白板面试其实更好,因为至少它可以考察你的智商和思维方式。
那么接下来,让我们谈谈每个人都应该关注但常常忽视的头号问题:
开发者体验
在绝大多数项目中,开发者体验是最重要的事情。每个人都希望快速发布以实现盈利,但最终他们往往在一个月内完成了 90% 的应用程序开发,而剩下的 10% 却需要三个月才能完成。
我理解,开发者总是对新项目充满热情,想要尽快展示成果以获得满足感,并让经理满意。虽然短期内这确实能让经理高兴,但从长远来看,每个人都会陷入恐慌,并在 4-5 年后考虑重构甚至从头开始重建那款应用程序——这就是为什么实际创建功能变得非常困难,而将其推向生产更是难上加难,于是便产生了滚雪球效应。
下面让我们来看两个代码示例:两个控制器,用于从数据库中获取热门用户并为其附加表情符号(来自我的一个开源项目 reporanger.xyz 的代码)。
这是从路由调用的控制器,其中包含所有功能以及一个 try-catch 块,用于检查是否存在错误:
// users.controller.ts
const getTrendingUsers = async (_req: Request, res: Response, _next: NextFunction) => {
try {
const events = await GithubEvent.find({
where: { event_date: MoreThan(new Date(Date.now - 24 * 60 * 60 * 1000)) },
order: { event_size: 'DESC' },
take: 3,
});
const users = await Username.find({ where: { id: In(events.map((event) => event.username_id)) } });
const topUsers = await getTopUsers(3);
const trendingUsers = await Promise.all(
users.map(async (user) => ({
...user,
emoji: await emojiService.getEmoji(user.score, topUsers),
})),
);
res.status(200).json({
status: 200,
message: 'Trending users fetched successfully',
data: trendingUsers,
error: '',
success: true,
});
} catch (error) {
res.status(500).json({
status: 500,
message: 'Error fetching trending users',
data: ,
error: error.message,
success: false,
});
}
};
下面是同样的控制器,但我们应用了一个简单原则:单一职责原则。
我们将代码拆分成了 4 个更小且可复用的文件:
● user.controller.ts
● user.service.ts
● async.util.ts
● response.util.ts
经过这样的优化后,带来的好处远超我们的想象!
我们可以在应用程序的任何地方重用这些代码。比如,在定时任务中我们可以调用
userService.getTrendingUsers 来获取热门用户信息。
我们移除了所有 try-catch 语句块,让代码更加简洁。同时,对于每个错误我们都进行了日志记录(例如使用 logger('error', error))。这样一来就可以很容易地创建一个错误服务,将所有错误信息存储到数据库中,为未来的应用场景做好准备。
另外,通过统一所有控制器的响应方式(如使用 resFn)也非常重要,它可以确保所有请求都能以相同的格式返回响应,正如下面示例中的简单泛型所示。
如此一来,开发一个新的控制器就变得轻松十倍,因为我们整个应用程序的编码架构非常一致。即使后来由其他开发者编写代码,也不会影响整体的编码风格。
// user.controller.ts
const getTrendingUsers = asyncFn(async (_req: Request, res: Response, _next: NextFunction) => {
const trendingUsers = await usernameService.getTrendingUsers;
resFn(res, {
status: 200,
message: 'Trending users fetched successfully',
data: trendingUsers,
error: '',
success: true,
});
});
// user.service.ts
const getTrendingUsers = async => {
const events = await GithubEvent.find({
where: { event_date: MoreThan(new Date(Date.now - 24 * 60 * 60 * 1000)) },
order: { event_size: 'DESC' },
take: 3,
});
const users = await Username.find({ where: { id: In(events.map((event) => event.username_id)) } });
const topUsers = await getTopUsers(3);
const usersWithEmoji = await Promise.all(
users.map(async (user) => ({
...user,
emoji: await emojiService.getEmoji(user.score, topUsers),
})),
);
return usersWithEmoji;
};
// async.util.ts
export const asyncFn = (fn: asyncPropsFunction) => async (req: Request, res: Response, next: NextFunction) => {
try {
await fn(req, res, next);
} catch (error) {
logger('error', error);
next(error);
}
};
// response.util.ts
export const resFn = (res: Response, { status, error, data, message, success }: IResponse<any>) => {
const suc = success !== undefined ? success : true;
res.status(status).json({
error,
data,
message,
success: suc,
status,
});
};
// response.interface.ts
export interface IResponse<T> {
status: number;
message: string;
data: T | any;
error: string;
success: boolean;
}
设想一下:假如我们从一开始就没有实现良好的架构,那么在未来想要对应用程序做一些小的改动时会怎样呢?哪怕只是想记录错误,我们也需要去每一个控制器中添加 logger('error', error);或者我们还想在响应中增加一个额外字段,比如 metadata?那必将会是一场噩梦。
首先进行重构
我觉得,重构应当在编写代码之前就做好:因为每个应用程序最终都需要重构。例如,对于一个拥有超过 70,000 行代码的大型软件项目来说,重构可能需要 30-40 个小时,在此过程中还会引入大量错误和 bug。
最终,你可能会不小心破坏应用程序,或者再花费 20 个小时进行测试。而且,当你的项目达到如此大的规模时,重构效果也不会像一开始就做重构那么理想。
我的建议是,在最初投入 40-50 个小时来规划和重构。只需创建几个控制器,集思广益地考虑如何在未来扩展这些控制器,然后进行重构,之后再继续开发。我知道,一开始你的经理可能会抱怨,因为你花了 50 个小时编码,但几乎没有什么实质性成果可以展示,只有一个能够良好扩展但没人能立即理解的架构。但如果条件允许的话,我觉得还是应该这样做。这不仅会为你自己,也会为未来的开发者省去很多麻烦。
单元测试同样非常重要。不要过分追求覆盖率,保持 60% 以上的覆盖率就足够了,这将在未来帮你避免大量潜在的 bug。
提交前检查
这一点非常重要。对于 JavaScript/TypeScript 项目,我们可以用 Husky。当然,针对不同的语言或框架还有很多其他选择。简单来说,Husky 是一个工具,它会在你提交代码之前运行一些命令。如果出现错误,那提交就不会通过。下面是一个来自 reporanger.xyz 的配置示例:
1、Lint Check(代码风格检查)
2、Run Tests(运行测试)
3、Prettify(代码格式化)
这三个简单的步骤,就能让你的代码库质量提升 10 倍。
#!/bin/sh
npx eslint --max-warnings=0 src api/src || {
echo "ESLint check failed. Commit aborted."
exit 1
}
cd ./api && npx jest || {
echo "Tests failed. Commit aborted."
exit 1
}
cd .. && npx prettier --write .
git update-index --again
简而言之
有很多方法可以用来改进你的代码,而我上面提到的这些做法其实并不难实现。尤其现在有了大模型(LLM)的帮助,很多问题往往不是你能不能做,而是你是否愿意去做。
抛开无聊的情绪,你会发现这样做会带来很多好处。开始带着热爱去编码,而不仅仅是为了钱。如果你能做到这一点,你会赚到更多的钱,让你的同事开心,你的经理也会感谢你,因为——编程不仅仅是写代码,更是一种架构设计。
相关推荐
- C语言学习从内存堆栈视角,给这段枚举代码做个 "内存透视"
-
从内存堆栈视角,给这段枚举代码做个"内存透视"#include<stdio.h>enumDAY{MON=1,TUE,WED,THU,FR...
- Python基础:枚举,都有哪些特点和使用场景呢?
-
在Python编程语言中,枚举(Enumeration)是一种特殊的类,用于为一组常量创建一个名称空间。枚举类在Python3.4中被引入,提供了一种更加直观和方便的方式来处理一组相关的常量。枚举类...
- Java枚举你真的会用吗_java枚举怎么使用
-
概述Java中枚举,大家在项目中经常使用吧,主要用来定义一些固定值,在一个有限的集合内,比如在表示一周的某一天,一年中的四季等。那你了解枚举的本质吗?了解枚举的一些常见用法吗?枚举介绍和使用枚举主要用...
- 反射、枚举以及Lambda表达式_反射getmethod
-
一、反射1.定义Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(即使是私有的);对于任意一个对象,都能够调用它的任意方法和属性,那么,我们就...
- 一个高效使用cursor开发项目的方法,怎么让 AI 写的代码不跑题?
-
最近又用cursor做了一个小应用,番茄时钟,用来管理自己的时间,提高效率。然后使用cursor开发的过程中。有了一些新的感悟。找到了一条可以让Curosr不跑题的办法。生成一份详细的项目资...
- 前端铜九铁十面试必备八股文——工程化
-
常用的git命令gitclone:克隆远程仓库到本地。gitinit:在当前目录初始化一个新的Git仓库。gitadd:将文件添加到暂存区,准备提交。gitcommit-m"co...
- IntelliJ IDEA 2025.2 的主要更新亮点
-
以下是该版本的一些关键改进与功能更新:AI增强体验离线Java代码补全:AI助手现在支持在离线模式下进行Java代码块建议,并允许用户选择本地代码模型使用。AIAssistant新增...
- 一行命令,AI 直接写代码!OpenAI 正式发布 Codex CLI
-
【一句话速读】OpenAI把2021年的Codex品牌复活,推出全新CodexCLI——一个本地运行的轻量级编码代理。只需npmi-g@openai/codex,它就能在终端里帮...
- 如何使用高级TypeScript模式构建可扩展的QA框架
-
TypeScript自动化QA(7部分系列)TypeScript第一步:自动化QA实用路线图如何在TypeScript中使用数组和对象构建强大的QA自动化脚本如何掌握TypeScript基础...
- Bun JS工具包新增MySQL驱动和密钥管理功能
-
Bun团队发布了其JavaScript打包器和运行时的1.2.21版本,该工具使用Zig语言编写,新增了包括MySQL和SQLite内置驱动、YAML解析器以及用于工具和本地开发的密钥管理器等功能。新...
- 编码 10000 个小时后,开发者悟了:“不要急于发布!”
-
【CSDN编者按】在软件开发的道路上,时间是最好的老师。根据“一万小时定律”,要成为某个领域的专家,通常需要大约一万小时的刻意练习。本文作者身为一名程序员,也经历了一万小时的编程,最终悟出了一个道理...
- 一文说明,TypeScript 的装饰器_typescript logo
-
●装饰器(Decorators)●注意:装饰器目前是一项实验性特性,在未来的版本中可能会发生改变●装饰器一般使用在以下几个地方○类○类属性○类方法○类方法的参数○通过这些我们也能看得出来,...
- 前端小哥哥:如何使用typescript开发实战项目?
-
前言笔者上一篇文章:主要写了typescript的用法和核心知识点总结,这篇文章将通过一个实际的前端案例来教大家如何在项目中使用typescript.你将收获如何使用umi快速搭建一个基于React...
- 一篇文章搞懂TypeScript_typescript implements
-
TypeScript是JavaScript的超集,一方面给动态类型的js增加了类型校验,另一方面扩展了js的各种功能。原始数据类型字符串数值布尔nullundefinedSymbolBi...
- TypeScript的any和unknown,用错一个就是线上Bug
-
在TypeScript开发中,类型系统是我们抵御运行时错误的第一道防线。但两个特殊类型——any和unknown,却常常被误用,成为线上故障的隐形推手。本文通过真实案例解析,告诉你为什么unknown...
- 一周热门
-
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)