Go 项目刚起步时,包结构宁可少一点也别先分太细
很多人刚写 Go 项目时,最有安全感的动作就是先把目录搭完整:controller、service、dao、model、utils 先分好,觉得以后扩展会方便。可项目在功能还不稳定的时候,包分得太细,往往比全写在一个地方更容易把自己绕进去。
很多人刚写 Go 项目时,最有安全感的动作就是先把目录搭完整:controller、service、dao、model、utils 先分好,觉得以后扩展会方便。可项目在功能还不稳定的时候,包分得太细,往往比全写在一个地方更容易把自己绕进去。
刚开始学 Go,并发部分最容易让人紧张的词就是“死锁”。这当然没错,因为死锁一出现,程序常常会非常明显地挂在那里。可等你真正把 Go 服务跑起来一段时间后,会发现另一类问题更难受:goroutine 没有马上死掉,而是在后台一点点堆起来。
Java 项目里很容易出现一种“看上去很稳”的异常处理:每一层都在 catch,每一层都在包一层自己的提示,最后用户确实看不到原始异常了,但开发排查时也很难再看到真正的现场。
2017 年我在处理一些线上问题时,对这一点的体会越来越深。
问一个很现实的问题:为什么很多 Express 项目都有认证中间件、日志中间件、跨域中间件,却偏偏把错误处理中间件留到最后才补?
我的答案是,因为它不像“功能”那样立刻可见,只有出问题时你才会想起它。
2016 年接手 Java 项目时,我遇到过一种很典型的混乱:同样是处理用户逻辑的类,有的叫 UserDao,有的叫 UserMapper,有的叫 UserServiceImpl,还有的直接叫 UserManager。项目还能跑,但只要需求稍微一变,大家就会先花时间猜“这个类到底管什么”。
图片上传这类接口最容易在需求讨论里被说成一句话:选文件,传上去,返回地址。
可 2016 年我在做这类功能时越来越明显地感觉到,一个上传接口如果一开始只关注“体验顺不顺”,却没有先限制大小、类型和错误边界,后面出问题会非常频繁。
2016 年做接口优化时,缓存几乎是最容易被提起的方案。接口慢了,加缓存;数据库忙了,加缓存;列表页扛不住了,还是加缓存。可真正做过几轮之后,我反而越来越谨慎,因为缓存很少是“开了就完事”的提速按钮。
2014 年做内容站和后台系统时,MongoDB 很吸引人,原因很简单:模型灵活、起步轻、开发速度快。可一旦列表页慢下来,很多团队的第一反应都是“是不是该上副本集”“是不是该多加几台机器”。我后来发现,这种反应往往太快了。
列表页做缓存,最先想到的写法通常很直接:第一页缓存成一个 key,第二页再来一个 key。
刚开始看完全没问题,可 2014 年我在给后台列表和内容列表做 Redis 缓存时,很快就遇到现实难题:真正决定一页内容的,根本不只是页码。
2014 年前后,Node.js 配 Redis 是很常见的一套轻量组合。大家一开始都觉得 Redis 上手快,set、get 会用就能干活,但真把它放进登录态、验证码、计数器、页面缓存之后,问题很快就来了:key 越堆越多,过期时间各写各的,最后自己都看不懂线上到底存了什么。