-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathatom.xml
More file actions
491 lines (299 loc) · 226 KB
/
atom.xml
File metadata and controls
491 lines (299 loc) · 226 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Junedayday Blog</title>
<subtitle>六月天天的个人博客</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://example.com/"/>
<updated>2023-05-09T06:12:29.586Z</updated>
<id>http://example.com/</id>
<author>
<name>Junedayday</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>【每周小结】2023-Week19</title>
<link href="http://example.com/2023/05/07/weekly/weekly-2023-19/"/>
<id>http://example.com/2023/05/07/weekly/weekly-2023-19/</id>
<published>2023-05-07T10:00:00.000Z</published>
<updated>2023-05-09T06:12:29.586Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-偶现的问题怎么解?"><a href="#Go技巧-偶现的问题怎么解?" class="headerlink" title="Go技巧 - 偶现的问题怎么解?"></a>Go技巧 - 偶现的问题怎么解?</h2><p>在我们日常排查异常时,必现的问题不难解决,让开发者们头痛的问题往往是偶现的。</p><p>我将偶现的问题分为两类 - <strong>程序类</strong>与<strong>业务类</strong>,分开进行讲解:</p><h3 id="程序类问题-注重可观测性的埋点"><a href="#程序类问题-注重可观测性的埋点" class="headerlink" title="程序类问题 - 注重可观测性的埋点"></a>程序类问题 - 注重可观测性的埋点</h3><p>程序类问题,包括<code>Go</code>语言中的<code>panic</code>、请求超时、内存泄露、死锁等。对于这类问题,我们优先会尝试各种途径去复现;如果短时间里无法复现,建议尝试如下3个思路:</p><ul><li><strong>从现象推导根因,不断缩小问题范围</strong> :如程序崩溃了,可以通过监控图、日志等途径,逐项分析,偶现的问题也往往是有迹可循的。</li><li><strong>适当添加测试</strong>:偶现类的问题往往是性能原因,可以尝试着用测试(尤其是<code>Benchmark</code>)去发现一些问题。</li><li><strong>查缺补漏,为下一次异常进行准备</strong>:偶现问题往往因为缺少数据的埋点,导致无法进一步分析;那我们只能补充相关的埋点信息,在下次出现问题时快速且精确定位。</li></ul><p>不难看到,程序类问题的核心在于 <strong>提高程序的可观测性</strong>,就是在<strong>程序的关键位置埋下数据,并利用监控图等工具去分析这些数据</strong>。这类问题一方面需要成熟的开发框架与基础平台的支撑,另一方面也依赖开发者的经验沉淀。</p><h3 id="业务类问题-注重业务对象的设计"><a href="#业务类问题-注重业务对象的设计" class="headerlink" title="业务类问题 - 注重业务对象的设计"></a>业务类问题 - 注重业务对象的设计</h3><p>业务类问题,指的是和具体业务系统相关的故障,比如:订单系统出现了几笔金额异常的订单、用户系统偶现登录异常、支付系统有时发生超时但仍显示支付成功等。</p><p>业务偶现类的问题,往往是业务代码的兼容性、健壮性不足,导致在特殊的场景下出现异常。</p><p>但是,业务代码往往有复杂的逻辑,如果直接从现象来分析,无疑是大海捞针。为了减少复杂度,最好利用面向对象的特性 - <strong>定位到关键问题的对象,并寻找对应的方法</strong>。对应到代码中,主要分两步:</p><ul><li><strong>定位到关键问题的对象</strong> - 找到代码中的对象,分析其数据结构与方法</li><li><strong>寻找对应的方法</strong> - 对异常的问题,找到对象中相关的方法</li></ul><p>上面的思路看起来很简单,但大部分用<code>Go</code>语言写的业务代码都是过程性的,做不到这一点。</p><p>比如,在对订单进行增删改查,并不是通过 <strong>订单对象</strong> 去操作的,而是直接用<code>GORM</code>库操作数据库来解决的。这两者有什么样的区别呢?我们以创建订单功能为例:</p><ul><li>面向对象的风格:我们只需要关注订单对象中的Create方法,所有对数据库的操作都封装在方法中;</li><li>面向过程的风格:我们需要全局搜索数据库的操作,找到其中创建订单的SQL语句;</li></ul><p>读懂了对象与对应的方法,再配合一定的日志,业务类问题(抛开基础组件的异常)基本可以被定位到了。将系统的复杂度集中在一个易于理解的对象中,是面向对象编程的一大价值。</p><blockquote><p>注意,如果系统中有“大对象”(例如订单内部包括多种订单类型),不利于分析,最好找时间优化为多个子类。</p></blockquote><h2 id="编程思考-重构方案的关注点"><a href="#编程思考-重构方案的关注点" class="headerlink" title="编程思考 - 重构方案的关注点"></a>编程思考 - 重构方案的关注点</h2><p>大部分的开发者都经历过遗留系统的维护工作,整天在稳定性、开发效率、新需求迭代等工作中头痛不已。如果遗留系统具有长期价值,重构工作会被提上日程。</p><p>上一篇已经分享了重构工作的整体考量点,以下三点会聚焦于技术层面的重构方案细节:</p><ul><li><strong>核心关注:重构工作的价值</strong> - 重构方案的价值,一般是修复某些历史问题,或者能大幅提升后续需求的效率。价值点要强调两个:<strong>价值要足够大,并且收益是确定性的</strong>(千万不要出现某个模块后续没啥需求了,还在那边谈提效)。</li><li><strong>守住底线:兜底能力的保障</strong> - 兜底能力,就是在出现问题时能快速切回到现状,千万不要因为重构引入更大的问题。在大型重构时,尽量保留原模块代码,复制出一份再进行重构,在<code>Go</code>语言中可以用<code>package</code>或者目录进行隔离,稳定运行一段时间后再删除。</li><li><strong>推进思路:结合已知需求,阶段性呈现效果</strong> - 重构需要周期,但周期过长很容易导致方案被否决;而如果你能将已有的需求与重构工作结合起来,适当地延长周期(比如某个需求开发需要5天,单独的重构需要10天,而结合起来只需要10天),会更容易得到业务与技术的一致认可。</li></ul><h2 id="工作生活-聊聊当下的就业环境"><a href="#工作生活-聊聊当下的就业环境" class="headerlink" title="工作生活 - 聊聊当下的就业环境"></a>工作生活 - 聊聊当下的就业环境</h2><p>近几年,程序员的就业环境一直比较恶劣,大量的裁员与为数不多的就业岗位压缩着工作机会。尽管现实比较残酷,但我通过周围的圈子关注到如下:</p><ul><li><strong>现实中的就业环境比舆论中的好不少</strong> - 负面消息会被自媒体放大,而正面消息相对被压缩。</li><li><strong>人才需求的两极化</strong> - 目前招聘的岗位要么偏向于经验资深的人才,要么是极具性价比的优秀应届生,资历平平的基层开发者很难在市场上寻找对口的位置。</li><li><strong>招聘途径更倾向于内部渠道</strong> - 随着市场萎缩,负责招聘的HR人力也缩减不少,大部分公司更倾向于内部渠道,优势不仅在于省下成本,更是有利于岗位的精确匹配。因此,我们在要注重各圈子的人脉建设。</li></ul><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week17</title>
<link href="http://example.com/2023/04/23/weekly/weekly-2023-17/"/>
<id>http://example.com/2023/04/23/weekly/weekly-2023-17/</id>
<published>2023-04-23T10:00:00.000Z</published>
<updated>2023-04-28T03:03:12.772Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-缺少项目经验的开发者该如何面试?"><a href="#Go技巧-缺少项目经验的开发者该如何面试?" class="headerlink" title="Go技巧 - 缺少项目经验的开发者该如何面试?"></a>Go技巧 - 缺少项目经验的开发者该如何面试?</h2><p>最近,我面试了几个应届生,他们的综合水平都挺不错的,但或多或少非常在意自己的项目经历,有的甚至会编造出一些内容,深入追问后很容易发现破绽。</p><p>今天,我就从一个面试者的视角出发提出三个建议,希望对广大应届生或者想转型做Go语言的读者有所帮助。</p><h3 id="掌握业界的主流解决方案"><a href="#掌握业界的主流解决方案" class="headerlink" title="掌握业界的主流解决方案"></a>掌握业界的主流解决方案</h3><p>缺少项目经验,这个问题本身影响还是很有限的,但如果你什么背景都不了解,就大大缩小了和面试官交流的范围。从高性价比来说,我建议各位根据自己的求职方向,掌握3个复杂系统的主流方案。</p><p>比如,你希望从事云原生方向,那就可以了解Kubernetes、OAM交付模型、阿里云公开的架构资料;如果你要做后台开发,那可以去看看较为通用的订单系统、用户系统、支付系统的架构。</p><p>相关资料在网上都可以找到,尽量根据发布时间、社区质量、受关注度等综合因素,挑选相对高质量、详略得当的文章仔细研读,一般花几个时间就可以入门一套系统。学习时,主要关注以下3点:</p><ol><li>了解系统中的<strong>高频术语</strong>,不要在面试时发生听不懂的情况</li><li>了解系统下<strong>各子模块与中间件的分工</strong></li><li>了解系统的<strong>基本流程</strong>,如一个pod是怎么创建的、一个订单是怎么生成的</li></ol><p>如果时间充裕,可以针对性地深入。</p><h3 id="重视问题的思考过程"><a href="#重视问题的思考过程" class="headerlink" title="重视问题的思考过程"></a>重视问题的思考过程</h3><p>我们或多或少都有一些小项目经验,哪怕只是自己开发的一个demo,也会是面试官的重要考量项。尽管这个项目本身难度低、也没有什么大价值,但优秀面试官看重的是思考过程,你要从思考过程中展示出自己的能力。</p><blockquote><p> 捎带一句,如果面试官只看重项目本身的价值,那就意味着公司需要的是资深人员,你与岗位不太匹配。</p><p> 我建议坦诚地说明这点,并向他展示经验以外的能力。</p></blockquote><p>如何从思考过程体现能力,我推荐3个思路:</p><ol><li><strong>技术深度 - 技术问题的有条理排查</strong>:如<code>HTTP</code>的异常排查,涉及到<code>Go</code>框架、HTTP协议、tcpdump等技术细节</li><li><strong>用户思维 - 从用户维度的功能设计</strong>:能描述清楚一个功能的从设计到开发的思路,从使用者的角度考虑问题</li><li><strong>技术广度 - 开源方案的横向对比</strong>:将自己的项目与开源方案进行对比</li></ol><p>其中,技术广度中开源方案的内容可以与 <strong>掌握业界的主流解决方案</strong> 结合,但千万要注意重点:<strong>小项目往往很难超越开源方案,我们可以适当描述在设计时的取舍思路,而不要过于强调开源方案的不足。所谓的“不足”很可能暴露出你的认知不足。</strong> </p><h3 id="重视所呈现的个性品质"><a href="#重视所呈现的个性品质" class="headerlink" title="重视所呈现的个性品质"></a>重视所呈现的个性品质</h3><p>面试官在对面试过程总结时,需要总结出几个关键词,也就是给面试者打上“标签”。这些标签一定程度上是可控的,也就是可以由面试者主动呈现出来 - 无论是通过简历,还是面试过程。这里,我强调几个我比较看重品质:</p><ul><li><strong>严谨</strong>:如简历中出现大量数字指标,并在面试过程中一一回答清楚</li><li><strong>思路清晰</strong>:对一个问题的排查思路,过程十分清晰自然,结果水到渠成</li><li><strong>善于沟通</strong>:在开发过程中,能频繁地与资深人员(公司leader或学校mentor)交流,调整方向</li></ul><p>呈现出的“标签”因人而异,在秉承着<strong>真实</strong>的前提下,也需要考虑贴合对应的公司、部门的特点。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>项目经验是一个资深开发者的核心价值所在。当行业缺口大时,可能存在跨方向转岗的情况;而当下整个就业环境较差,这种情况已经非常少见了,这就要求转岗的朋友适当降低预期。</p><p>而对于应届生或者找实习的同学,如果想要获得满意的offer,可以从上述3个维度切入,整理自己的简历、准备面试,可以提升不少的竞争力。有想要改进简历的朋友,可以直接联系本人,不定期回复。</p><h2 id="编程思考-普通工程师如何在组织中推进系统重构"><a href="#编程思考-普通工程师如何在组织中推进系统重构" class="headerlink" title="编程思考 - 普通工程师如何在组织中推进系统重构"></a>编程思考 - 普通工程师如何在组织中推进系统重构</h2><p>作为一名普通工程师,在公司内部推动重构类工作是很困难的,常见的问题有:</p><ul><li><strong>短期价值不高</strong>:重构类工作短期很难拿到足够大的收益</li><li><strong>推动途径难</strong>:往往需要自下而上、跨部门的协作</li></ul><p>我整理了近几年相关的工作经历,总结如下:</p><ul><li><strong>重构方案是核心</strong> :重构的技术方案是核心,它决定了最终的成败</li><li><strong>自下而上地介绍价值,自上而下地推动工作</strong>:项目的价值点必须得到上层认可,再由上层推进工作</li><li><strong>从各团队的视角地看问题</strong>:自上而下地推动工作可以把控整体节奏,但如果想要有序、可控地推动,很需要你带入他人的视角,用共情的思维来看待具体问题</li><li><strong>时间是最大的变量</strong>:无论是价值还是投入,时间总是最大的变量,耐心才是最重要的品质</li></ul><p>总的来说,重构类工作是一个综合考验技术、沟通以及耐心的过程。关于重构方案,我后续会补充一个示例。</p><h2 id="工作生活-快速修复小问题"><a href="#工作生活-快速修复小问题" class="headerlink" title="工作生活 - 快速修复小问题"></a>工作生活 - 快速修复小问题</h2><p>生活中有很多不尽如人意的小事情、小情绪。其中,影响我最大的是 <strong>沉没成本</strong> 这个因素。</p><p>比如我的钥匙丢了,如果一直批判自己是怎么丢的、以后要采取哪些手段来避免等等,这种情绪就容易演变成更大的问题;而如果我快速地采取行动去补钥匙,在路上回想一下丢失钥匙的过程,既解决了问题,又有了反思。</p><p>对一些小问题、小情绪,我习惯于快速着手去解决,规避后续产生更多的损耗。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week16</title>
<link href="http://example.com/2023/04/16/weekly/weekly-2023-16/"/>
<id>http://example.com/2023/04/16/weekly/weekly-2023-16/</id>
<published>2023-04-16T10:00:00.000Z</published>
<updated>2023-04-21T09:04:07.681Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-3个快速提升Gopher编程能力的思维"><a href="#Go技巧-3个快速提升Gopher编程能力的思维" class="headerlink" title="Go技巧 - 3个快速提升Gopher编程能力的思维"></a>Go技巧 - 3个快速提升Gopher编程能力的思维</h2><p>作为一名长期在一线coding的编程人员,我见证了很多同学的成长过程。我总结下来,成长速度主要看两大方面:</p><ul><li>开发团队:包括开发项目、团队氛围等</li><li>个人内驱:自己的性格、思维模式等</li></ul><p>对我们来说,更有效、可控的方法是调整自己的思维模式,实现快速成长。结合<code>Go</code>语言的特点,这里我推荐3个比较推崇的思维:</p><h3 id="抓住问题,深挖到底"><a href="#抓住问题,深挖到底" class="headerlink" title="抓住问题,深挖到底"></a>抓住问题,深挖到底</h3><p><code>Go</code>语言可以算是最简单的编程语言之一了,开发者往往能相对快速地完成开发工作,但机械式的工作很难让人快速成长。这时,日常工作中的“问题”就是一个突破口。</p><p>问题有很多种,也很琐碎,比如 调用某个库出现异常、代码发生循环依赖、线上服务突然崩溃等等。如果只是为了临时性地解决,可能花费不了多少时间,但这些问题的背后往往是软件工程的基本问题,是一个很好的积累过程。如果你肯进行深挖,往往会得到如下有价值的沉淀:</p><ul><li><code>Go</code>语言之下的操作系统、网络等基础知识</li><li><code>Go</code>项目的框架、各开源库的使用</li><li>软件工程的管理</li></ul><p>不同方向的沉淀有多有少,对我们的价值不仅在于知识的积累,更是过程中对思维的锻炼。</p><h3 id="扩大眼界,不要给自己设限"><a href="#扩大眼界,不要给自己设限" class="headerlink" title="扩大眼界,不要给自己设限"></a>扩大眼界,不要给自己设限</h3><p>上一点,相信大部分的同学努努力就可以达到。但是,困扰我们的往往是努力的方向 - 我的职业生涯也深受影响。</p><p>如果有了一个明确的目标,比如说去研究<code>Go</code>框架的问题,梳理清楚内部的逻辑,这个事情是能够轻松完成。不过,明确的目标 本身就有一个限制:如果目标很明确,往往意味着在舒适圈之内。而成长,往往需要突破舒适圈。</p><p>以上面的<code>Go</code>框架问题为例,会有什么更具挑战性的内容呢?</p><ul><li>横向对比<code>Go</code>的开源主流框架,思考问题是否来自于框架的本身设计问题?</li><li>探索框架本身的历史,研究问题引入的背景,是否会引入新的问题?</li></ul><p>这些思考要求我们有更大的视野,也颇具挑战。蓦然跨出舒适圈,大部分人都会感到不适,但克服这种不适、突破自己的现有上限,不仅需要强大的内心,也需要资深人员的指点。</p><h3 id="以终为始,升华过程性思维"><a href="#以终为始,升华过程性思维" class="headerlink" title="以终为始,升华过程性思维"></a>以终为始,升华过程性思维</h3><p>通过前两点,我们进入了一个不断进步的“终身成长”模式。提升能力是无限的,而人生是有限的,我们需要有规划地投入。</p><p>而随着开发经验的积累,我们需要有“以终为始”的思维:</p><ul><li>设计技术方案时,要考虑数据埋点、预估排期</li><li>评估需求优先级时,要综合最终需求的价值</li><li>述职时,不仅要说出“做了什么”,更要说清“为什么做”</li></ul><p>以终为始,是需要我们从ROI的角度来统筹全局,更好地规划自己的工作,乃至人生。</p><h2 id="编程思考-系统之间的协作路径"><a href="#编程思考-系统之间的协作路径" class="headerlink" title="编程思考 - 系统之间的协作路径"></a>编程思考 - 系统之间的协作路径</h2><p>在维护现有系统时,外部依赖是一个高频发生问题的点,尤其是在微服务场景下。</p><p>外部系统的不确定性主要来源于变更,表现出来的现象如 <strong>不可用</strong> 与 <strong>不兼容</strong>。不可用的责任方很明确,解决方案也比较常规,这里暂不讨论。</p><p>对开发者来说,更为头痛的问题是不兼容。不兼容的原因常是外部系统的升级,出现数据结构不一致的问题,没有及时地通知到各个依赖方、或各依赖方来不及做对应的升级。可见,研发团队之间的协作路径设计非常重要。</p><p>这里,我列举2个常见的协作路径(以RPC调用为示例):</p><ol><li><strong>存量变更必须向下兼容</strong>:这个是最常用、最稳定的方案</li><li><strong>新版本发布并平滑迁移</strong>:先发布新版本,并确定迁移周期,如1个月:各依赖方如果有发布,就必须引用新版本;到1个月后,强制废弃旧版本</li></ol><p>方案1虽然简单,但也是出现大量遗留代码、降低可维护性,时至今日,很多公司仍将其作为第一选择,背后也是为了强调“稳定性”;方案2看起来也不难,但实际能落地的公司或团队屈指可数,它非常看重 <strong>基础设施配套</strong> 与 <strong>团队文化</strong>。</p><p>系统变更的复杂性往往不在于技术,下周我进一步聊聊个人的一些心得。</p><h2 id="工作生活-选择性地投入情绪"><a href="#工作生活-选择性地投入情绪" class="headerlink" title="工作生活 - 选择性地投入情绪"></a>工作生活 - 选择性地投入情绪</h2><p>从精力来看,一天能投入的情绪能量(无论正面还是负面)往往是有限的:我们很少能见到在工作时能一直充满热情,回家后又能积极且耐心地辅导孩子作业的人。</p><p>对成年人来说,精力越来越受限,我们可以尝试着进行做一些调整:</p><ul><li>尽量投入积极情绪,而避免负面情绪的损耗</li><li>对高频重复的事情形成方法论,尽量用习惯的力量去推进完成</li><li>适当放空自己,如运动、冥想,提高恢复力</li></ul><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>五分钟技术小分享 - 分布式链路追踪Tracing</title>
<link href="http://example.com/2023/04/15/sharing-5min/2023/20230415/"/>
<id>http://example.com/2023/04/15/sharing-5min/2023/20230415/</id>
<published>2023-04-15T04:00:00.000Z</published>
<updated>2023-04-15T09:54:22.523Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/5min.jpg"></p><p>近几年,程序的可观测的理论非常火,但很多同学对其三个概念一知半解。</p><p><code>Logging</code>对应到日志,<code>Metrics</code>对应到监控指标,而<code>Tracing</code>的概念最为复杂。推荐大家可以看看这篇<a href="https://mp.weixin.qq.com/s/00aiWY5bX6RnAKL8UpAZyw">文章</a>作为入门。</p><p>最核心概念为2个:</p><ul><li><code>Trace</code> 一次请求的唯一标记。一般在入口处生成,串联了各程序、中间件之间的调用。常说的<code>traceid</code>是一次<code>Trace</code>的唯一标记</li><li><code>Span</code> 指具体的一个调用。如服务A调用了服务B、或服务A查询了MySQL,是不同的<code>Span</code>。<code>Span</code>有很多有意义的指标,如调用方法名、耗时、调用结果、父子关系,也可以添加自定义的标签<code>tag</code></li></ul><p>基于<code>Trace</code>与<code>Span</code>有很多玩法,但始终绕不开以下3点:</p><ol><li><strong>单个请求的视角</strong>:基于<code>Trace</code>的问题排查</li><li><strong>单功能模的视角</strong>:基于<code>Span</code>的QPS/失败率的统计</li><li><strong>系统视角</strong>:基于<code>Trace</code>和<code>Span</code>的治理</li></ol><p>前两点与开发同学的日常工作息息相关,而第三点则是基础架构组关注的重点,有许多衍生出来的玩法,如压测、容量规划等。</p><p>从社区来说,目前已基本由<a href="https://opentelemetry.io/">OpenTelemetry</a>实现标准的大一统,有兴趣的同学可以适当关注。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p> <img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/5min.jpg"></p>
<p>近几年,程序的可观测的理论非常火,但很多同学对其三个概念一知半解。</p>
<p><code>Lo
</summary>
<category term="每日技术分享" scheme="http://example.com/categories/%E6%AF%8F%E6%97%A5%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
<category term="Daily-Sharing" scheme="http://example.com/tags/Daily-Sharing/"/>
</entry>
<entry>
<title>【每周小结】2023-Week15</title>
<link href="http://example.com/2023/04/09/weekly/weekly-2023-15/"/>
<id>http://example.com/2023/04/09/weekly/weekly-2023-15/</id>
<published>2023-04-09T10:00:00.000Z</published>
<updated>2023-04-13T14:04:02.628Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-面对遗留Go代码,如何优化?"><a href="#Go技巧-面对遗留Go代码,如何优化?" class="headerlink" title="Go技巧 - 面对遗留Go代码,如何优化?"></a>Go技巧 - 面对遗留Go代码,如何优化?</h2><p>虽然 <code>Go</code> 语言的诞生时间不长,但由于语言特性、人员水平、框架选型等因素,存在各种“遗留代码”需要开发人员维护。尽管内心有各种不乐意,但如果该系统有足够的价值,我们还是不得不面对这个挑战。</p><p>那么面对遗留<code>Go</code>代码,我们该如何优化呢?</p><h3 id="梳理现状,明确目标"><a href="#梳理现状,明确目标" class="headerlink" title="梳理现状,明确目标"></a>梳理现状,明确目标</h3><p>首先,我们要对代码优化有一个提纲挈领的把控。代码优化就是一个项目,我们要梳理清楚输入与输出:</p><p>输入重点包括:梳理程序代码现状,以及预期投入到改造的人力。</p><p>而输出则关注预期改造后的收益。注意,这里的收益最好能有所区分:例如 不同改造方案、不同人力投入带来的收益会有所差异;同时,改造收益也要从各参与方的视角出发,如提升开发效能(开发人员)、提高项目的稳定性(管理人员)等。</p><h3 id="控制风险,小步迭代"><a href="#控制风险,小步迭代" class="headerlink" title="控制风险,小步迭代"></a>控制风险,小步迭代</h3><p>我们这里谈论的代码优化,虽然从改造内容来说,并不亚于一次规模较大的重构或重写。</p><p>但是,在团队内提出一个重构或重写的专项工作,第一时间给周围人的感觉是:<strong>高风险</strong>、<strong>长周期</strong>。一旦这个项目涉及到多方人员,往往推进难度极大。</p><p>而代码优化这个说法会显得更轻量,容易得到多方的支持;也同时提醒我们,整个改造过程要控制风险、小步迭代、适时回滚。改造中会有不少的阵痛期,也是开发人员快速成长的机会。</p><p>而当改造完成后,整个项目焕然一新,完全就是一次成功的大型重构。</p><h3 id="第一步:解决外部依赖代码"><a href="#第一步:解决外部依赖代码" class="headerlink" title="第一步:解决外部依赖代码"></a>第一步:解决外部依赖代码</h3><p>作为改造的第一步,我们应先清晰地隔离外部依赖的问题。</p><p>这一点,关键就是做好标准化<code>Go Module</code>的改造,移除历史遗留的<code>glide</code>、<code>vendor</code>等依赖方式。在陈旧的项目里,开发者会发现各种奇怪的问题。所幸,<code>Go</code>作为一门编译型语言,能提前发现很多语法不兼容的问题,减少风险。</p><p>完成了<code>Go Module</code>的改造后,项目对外部依赖库有了明确、清晰的管理方式。</p><h3 id="第二步:明确分层逻辑"><a href="#第二步:明确分层逻辑" class="headerlink" title="第二步:明确分层逻辑"></a>第二步:明确分层逻辑</h3><p>分层是项目内代码的“躯干”,是开发者理解代码的核心切入点。</p><p>分层方式有多种,很难统一标准;而且,在一个团队中,我们最好能根据团队成员、历史代码、组织情况灵活调整。无论如何选择,都应在团队内对分层逻辑保持一致,并进行改造。分层逻辑对代码的改造较大,我推荐按如下的方式进行:</p><ol><li><strong>先独立遗留代码</strong>:将遗留代码独立到一个专门的路径,如<code>legacy</code>,和标准的代码隔离</li><li><strong>用目录保证分层</strong>:分层后的代码用目录进行“物理隔离”</li><li><strong>逐步并有序地迁移</strong>:改造的过程一定要有章法,如<code>legacy</code>下的代码只删不增,分层下的代码符合规范</li></ol><p>这一阶段会花费大量的时间在迁移的过程,但决定成败的在于分层设计,前期必须做好充足的准备与可行性验证,才能保证最后交付出的价值。</p><h3 id="第三步:梳理context和error"><a href="#第三步:梳理context和error" class="headerlink" title="第三步:梳理context和error"></a>第三步:梳理<code>context</code>和<code>error</code></h3><p>在明确了分层逻辑后,有2个<code>Go</code>专有的特性非常值得开发者关注:</p><ol><li><code>context</code>上下文,传递一些公共变量(如用户)和超时等控制逻辑,核心数据流是 <strong>上层->下层</strong></li><li><code>error</code>错误,传递异常信息,核心数据流是 <strong>下层->上层</strong></li></ol><p>梳理清楚这两个变量的使用方式,能帮助我们串联起各分层的逻辑,也为后续的拓展能力做好储备,如基于<code>context</code>的<code>trace</code>传递,基于<code>error</code>的错误码封装。</p><h3 id="第四步:工具库升级"><a href="#第四步:工具库升级" class="headerlink" title="第四步:工具库升级"></a>第四步:工具库升级</h3><p>完成了分层与<code>context</code>/<code>error</code>的梳理,基本的代码框架已经形成,但最大的收益点仍没有达到:<strong>提效</strong>。我们要不断地替换或引入一些成熟的工具库,来提升研发效能。</p><p>在这一阶段,我们会不断遇到问题,但不要灰心、在半途选择放弃,这将是你成长最快的一个机会。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>我经历过大大小小近10个<code>Go</code>语言项目的大型重构,遇到过各种疑难杂症,也算是从摸爬滚打的经验里积累出一点心得。</p><p>目前,技术问题基本可以通过搜索引擎解决,更困难的是将这个代码优化的过程落地到组织中,逐步推进。这是一个深入融入到公司的机会。</p><h2 id="编程思考-基础团队为何推动技术迭代难"><a href="#编程思考-基础团队为何推动技术迭代难" class="headerlink" title="编程思考 - 基础团队为何推动技术迭代难"></a>编程思考 - 基础团队为何推动技术迭代难</h2><p>在中大型公司里,往往会建立一个基础团队,负责通用的、公共的技术,例如多语言工具库、各类基础平台。</p><p>对基础团队来说,由于不直接面向一线业务,也就无法直接根据业务收益来评估自身的价值;而基础团队的价值更多地是要从提效来体现,也就是降低各业务团队在通用能力上的投入。</p><p>提效,最常见的方法是引入一个更高效的技术、去替代相对低效的老技术,比如用git替代svn,用docker来替代vm。但是,即便新技术得到广泛认可,在各业务团队中依然很难推进。最常见的问题有两个:</p><ol><li>改造成本</li><li>稳定性风险</li></ol><p>这两个问题有很多解决思路,不过并不是今天讨论的重点。让我们先抛开技术视角,如果你是业务团队的负责人,你会考虑:</p><ol><li>改造有时间、人力成本,为什么不安排去做几个业务需求、提升产品效果?</li><li>稳定性有风险,为什么不维持现状?</li><li>既然推动新技术可能影响到业务价值,那是否可以不做?或者延期再做?</li></ol><p><strong>面对可能存在损失时,人们倾向于保守的态度。</strong>所以一旦涉及到业务方的技术迭代,单靠基础团队推动是极难的。</p><p>也许你会疑惑:业务团队不考虑提效吗?我的想法是:业务当然会考虑提效,但业务的提效点并不只能基础团队的技术,从业务团队内部做提效会更快捷、更可控。</p><h2 id="工作生活-试着给孩子“戴高帽”"><a href="#工作生活-试着给孩子“戴高帽”" class="headerlink" title="工作生活 - 试着给孩子“戴高帽”"></a>工作生活 - 试着给孩子“戴高帽”</h2><p>在中国式教育中,我们常常以批评、责备等方式来督促孩子,但效果常常适得其反。这时,不妨尝试着给孩子“戴高帽”,多夸奖孩子。</p><p>“戴高帽”并不意味着无脑地赞扬,而是希望家长们在有负面情绪时,先抑制住自己当前的情绪,尝试着去感受并理解孩子。真心夸奖往往需要先调整到正面的情绪,就督促着我们去努力发掘孩子行为背后的含义。</p><p><strong>调整情绪,主动地去理解孩子</strong>,这才是“戴高帽”最重要的意义。面对生活、工作中的其它负面情绪时,我们也可以用这种方式进行应对,也许会有意想不到的收获。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week14</title>
<link href="http://example.com/2023/04/02/weekly/weekly-2023-14/"/>
<id>http://example.com/2023/04/02/weekly/weekly-2023-14/</id>
<published>2023-04-02T10:00:00.000Z</published>
<updated>2023-04-05T11:31:31.475Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-如何编写可测试性好的代码"><a href="#Go技巧-如何编写可测试性好的代码" class="headerlink" title="Go技巧 - 如何编写可测试性好的代码"></a>Go技巧 - 如何编写可测试性好的代码</h2><p>很多文章都强调了代码要具备可测试性,也讲述了如何写单元测试。但是,这些单元测试都停留于演示的水平,在复杂项目中根本无法应用,导致单元测试只停留于理论。</p><p>今天,我尝试从复杂项目的层面入手,和大家分享我的整体思路。更具体的示例会在后面讲解。</p><h3 id="1-写测试的必要性"><a href="#1-写测试的必要性" class="headerlink" title="1 - 写测试的必要性"></a>1 - 写测试的必要性</h3><p>在编写测试前,你需要先真正地认可它对项目的价值。</p><p>从历史经验来看,绝大多数代码都无法被测试覆盖;而很多单元测试又因为代码变更失去效果。所以,我认为单元测试必须要覆盖的场景主要分2块:</p><ol><li><strong>通用工具库</strong>。鉴于工具库的影响面极大,对稳定性的要求极高,单元测试是一个重要且必要的质量保障手段。</li><li><strong>核心业务逻辑</strong>。核心业务逻辑有2个特点:价值高、变更小。</li></ol><p>这两块是对单元测试是价值最高的部分。当然,其余代码也可以写单元测试,但价值就没那么大了,可以按需应用。</p><blockquote><p>捎带提一句,目前有不少“唱衰”单元测试价值的人。</p><p>我的态度是:大家可以对单测的价值有不同的观点,但首先,你得学会怎么写好代码与单测。如果写不好代码,又反复贬低单测,这类人往往是 为自己的能力不足找借口。</p></blockquote><h3 id="2-梳理代码逻辑"><a href="#2-梳理代码逻辑" class="headerlink" title="2 - 梳理代码逻辑"></a>2 - 梳理代码逻辑</h3><p>要编写可测试性的代码,前提是你要清晰地理解这部分代码做了什么,才能对接下来的工作做到心中有数。</p><p>梳理的方式有很多,我个人比较喜欢从面向对象的角度进行切入,体现在3个问题:</p><ol><li>核心对象是什么?</li><li>依赖对象是什么?</li><li>核心对象+依赖对象是怎么工作的?</li></ol><p>回答了这三个问题,代码逻辑的框架也基本形成了。</p><h3 id="3-规划测试思路"><a href="#3-规划测试思路" class="headerlink" title="3 - 规划测试思路"></a>3 - 规划测试思路</h3><p>理清了代码逻辑后,我们千万不要直接上手敲代码,否则很容易演变成了“为了写测试而写测试”,越写越迷茫。</p><p>我个人习惯在动手前,花费5分钟左右的时间对这块代码做针对性的思考:<strong>如果要写单测,我希望有哪些测试点?</strong>这个问题是为了让自己接下来的工作有更明确的方向。涉及的问题如:</p><ol><li>哪部分代码最容易出错?</li><li>哪些外部依赖容易发生异常?</li><li>哪些代码对系统的价值最高?</li></ol><p>随着测试重点的明确,编写测试代码的思路也逐渐清晰。</p><h3 id="4-第零轮改造"><a href="#4-第零轮改造" class="headerlink" title="4 - 第零轮改造"></a>4 - 第零轮改造</h3><p>在写单测前就改造代码,看似和单元测试的原则相悖:不是应该先写好单元测试,再针对这部分代码做优化吗?这样才能保证改造前后的一致性。</p><p>这个说法没错,但事实上,复杂的业务代码因为存在各种依赖(中间件、外部服务等),直接编写单元测试的成本极高。在面对这类可测试性极差的代码时,我更倾向于先做一轮改造 - 不妨定义为 <strong>第零轮</strong> 改造,来减少编写单测的复杂度。</p><p>代码改造的风险是很高的,我建议从2个途径降低风险:</p><ol><li>用接口测试、系统集成测试等更高层面的测试来保证质量</li><li>第零轮改造尽量在初次提交代码时完成(也就是自己开发完后立刻优化),或者跟随小版本的迭代(大版本会提高排查的复杂度)</li></ol><h3 id="5-编写单元测试"><a href="#5-编写单元测试" class="headerlink" title="5 - 编写单元测试"></a>5 - 编写单元测试</h3><p>接下来,就是常规的单元测试编写了。这部分的方法网上有很多,不再赘述,这里仅分享我的核心思路:<strong>单元测试的覆盖面可以少,但校验要尽量严格</strong>。</p><p>有些单测只是为了一个覆盖率指标,完全不去检测<code>error</code>等错误,基本发现不了什么问题,也就失去了单测本身的价值。</p><p>以前我也常这么写,但随着第三步 - <strong>规划测试思路</strong> 的引入,我会反思自己写单测的初衷:要么从一开始就放弃写单测,要么就花时间写好它、用好它。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>希望大家能更聚焦于2、3、4这三步,打好地基,让个人与项目从单元测试中收益更多。</p><p>关于编写可测试性好的代码,后续我也会给出具体的代码case,让大家不仅掌握方法论,也能获得实战经验。</p><h2 id="编程思考-如何提高代码表达能力"><a href="#编程思考-如何提高代码表达能力" class="headerlink" title="编程思考 - 如何提高代码表达能力"></a>编程思考 - 如何提高代码表达能力</h2><p>很多人都觉得,程序员的形象是一个闷头敲代码的“极客”,非常内向。但是,程序员的发展离不开表达,无论是团队协作,还是展示个人能力。</p><p>我们可以在写代码时就有意识地锻炼表达能力。重点有如下3点:</p><ol><li>学习英文,尽量用代码本身表述自己的逻辑</li><li>适当注释,尤其是背景类信息、特殊处理</li><li>通读代码,串联上下文逻辑,不断打磨可读性</li></ol><p>最重要的是第三点,个人总结了3个经验:</p><ol><li>通读前,要不断清空脑子里的已知信息,以全新的视角代入</li><li>通读时,把握主次关系,逐字逐句打磨</li><li>通读后,要不断反问自己,目前的实现合理吗?可以优化吗?</li></ol><h2 id="工作生活-在起伏中提高下限"><a href="#工作生活-在起伏中提高下限" class="headerlink" title="工作生活 - 在起伏中提高下限"></a>工作生活 - 在起伏中提高下限</h2><p>生活自有起伏,我们很难把控住每个细节。就比如我的每周跑步计划,很容易因为工作情况、天气变化、身体状态等因素而波动,有时能跑个20km,而有时只有5km。</p><p>如果我从均值来制定一个目标,比如10km,那么就常常会发生达不到目标的情况,这就很容易让我有一定的心理负担,最终的效果并不会有所提高。</p><p>我最近尝试着根据下限来设计目标,如定下限5km+2km=7km作为目标,心态就有两点转变:</p><ol><li>跑到5km时,会激励自己再坚持一点点就能达成目标;</li><li>超过目标7km后,心态会更放松,反而能坚持更久。</li></ol><p>在生活中,往往你提高了下限,就能提高整体水平。希望我的经历能对你有帮助。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>五分钟技术小分享 - Github的RSA SSH问题</title>
<link href="http://example.com/2023/03/30/sharing-5min/2023/20230329/"/>
<id>http://example.com/2023/03/30/sharing-5min/2023/20230329/</id>
<published>2023-03-30T04:00:00.000Z</published>
<updated>2023-03-30T06:55:47.669Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/5min.jpg"></p><p>在2023-03-24,<code>Github</code>官方对自己的<code>RSA SSH host key</code>进行了更新。最直接的现象是用户与远端仓库连接时会报错:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@</span><br><span class="line">@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @</span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@</span><br><span class="line">IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!</span><br><span class="line">Someone could be eavesdropping on you right now (man-in-the-middle attack)!</span><br><span class="line">It is also possible that a host key has just been changed.</span><br><span class="line">The fingerprint for the RSA key sent by the remote host is</span><br><span class="line">SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s.</span><br><span class="line">Please contact your system administrator.</span><br><span class="line">Add correct host key in ~/.ssh/known_hosts to get rid of this message.</span><br><span class="line">Host key for github.com has changed and you have requested strict checking.</span><br><span class="line">Host key verification failed.</span><br></pre></td></tr></table></figure><p>官方的<a href="https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/">博客</a>给出了解决方案,主要分为两步、各有一个自动方案和手动方案:</p><ol><li>删除原key<ol><li>自动方案:执行命令<code>ssh-keygen -R github.com</code></li><li>手动方案:手动去文件<code>~/.ssh/known_hosts</code>里删除<code>github.com</code>的那行数据</li></ol></li><li>更新key<ol><li>自动方案:执行命令 <code>curl -L https://api.github.com/meta | jq -r '.ssh_keys | .[]' | sed -e 's/^/github.com /' >> ~/.ssh/known_hosts</code></li><li>手动方案:手动去文件<code>~/.ssh/known_hosts</code>插入数据,如下</li></ol></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=</span><br></pre></td></tr></table></figure><p>关于这个更新,Github官方的描述很模糊 - <code>out of an abundance of caution</code> ,翻译过来就是 出于谨慎的考虑。</p><p>而对应的公告发布时间也是在更新前一天,很仓促,并且前后并不兼容、对用户的体验很差,让我不得不怀疑是官方遇到了相关安全漏洞的问题才做的紧急更新。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p> <img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/5min.jpg"></p>
<p>在2023-03-24,<code>Github</code>官方对自己的<code>RSA SS
</summary>
<category term="每日技术分享" scheme="http://example.com/categories/%E6%AF%8F%E6%97%A5%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
<category term="Daily-Sharing" scheme="http://example.com/tags/Daily-Sharing/"/>
</entry>
<entry>
<title>【每周小结】2023-Week13</title>
<link href="http://example.com/2023/03/26/weekly/weekly-2023-13/"/>
<id>http://example.com/2023/03/26/weekly/weekly-2023-13/</id>
<published>2023-03-26T10:00:00.000Z</published>
<updated>2023-03-28T15:35:29.273Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-保障排查问题时的代码一致性"><a href="#Go技巧-保障排查问题时的代码一致性" class="headerlink" title="Go技巧 - 保障排查问题时的代码一致性"></a>Go技巧 - 保障排查问题时的代码一致性</h2><p>当我们排查复杂的线上问题时,往往需要结合开发环境的代码进行分析。但是,很多问题排查到最后,却发现线上和本地的代码是不一致的,浪费了大量时间。</p><p>那么,就让我们从整个代码发布的链路来分析会有哪些常见的坑。这里,我以<code>Go</code>语言以及主流的<code>git</code>、<code>Docker</code>、<code>Kubernetes</code>系统为例。</p><h3 id="1-本地的脏代码"><a href="#1-本地的脏代码" class="headerlink" title="1 本地的脏代码"></a>1 本地的脏代码</h3><p>线上问题往往发生在需求迭代过程中,我们本地环境还在开发过程中,有相当一部分的“脏代码” - 它们或者没有提交<code>commit</code>,或者没有推送到远端<code>push</code>。</p><p>这时,基于这些“脏代码”排查的问题很容易得出错误的结果。所以,一般建议 <strong>将发布分支和开发分支隔离</strong>,在排查时进行切换。</p><h3 id="2-切换分支异常"><a href="#2-切换分支异常" class="headerlink" title="2 切换分支异常"></a>2 切换分支异常</h3><p>一般发布到线上的代码都会指定分支,比如<code>master</code>,而排查问题时就切换到对应的分支。那这个简单的切换过程会出现问题吗?会,而且我就遇到过多次,常见的包括2种:</p><ol><li>切换分支失败。一般是由于当前分支有未<code>commit</code>的内容,切换时发生冲突</li><li>切换后没有拉取最新代码。本地的<code>master</code>落后于代码库中的最新版本。</li></ol><h3 id="3-线上版本并不是最新版本"><a href="#3-线上版本并不是最新版本" class="headerlink" title="3 线上版本并不是最新版本"></a>3 线上版本并不是最新版本</h3><p>为了提升研发效率,建议将线上版本与发布分支的最新版本保持一致。这样,用户就无需去查看线上的版本号,而是直接切换到对应分支的最新版本即可。</p><p>但是,从代码仓到线上经过了CICD流水线,并不一定能保证一致:</p><ol><li>上次发布失败了,线上的仍是老版本</li><li>线上版本发生了回退,但代码库没有回滚</li></ol><p>这类问题发生的频率也不低,值得我们关注。</p><h3 id="4-Go语言开发版本与编译版本不一致"><a href="#4-Go语言开发版本与编译版本不一致" class="headerlink" title="4 Go语言开发版本与编译版本不一致"></a>4 Go语言开发版本与编译版本不一致</h3><p>尽管<code>Go</code>语言的兼容性很好,但不同版本之间的差异还是存在的,并且发生问题时往往很隐蔽。</p><p>我们在维护本地<code>Go</code>语言版本时,一定要优先保证与编译所使用的版本。</p><p>如果你想尝试某些新版本的特性,也不要覆盖当前版本,而是更换新版本默认的安装路径。</p><h3 id="5-vendor的依赖库问题"><a href="#5-vendor的依赖库问题" class="headerlink" title="5 vendor的依赖库问题"></a>5 vendor的依赖库问题</h3><p><code>Go</code>语言的版本依赖方案已经由<code>Go Module</code>实现大一统了,而传统的<code>vendor</code>方案很容易出现各种奇怪的问题。</p><p>例如,我曾遇到过一个问题:有人修改了<code>vendor</code>目录中的依赖代码,但在编译环境时,会通过<code>go mod vendor</code>命令更新依赖,导致修改的内容被替换,造成了和预期不一致的内容。</p><h3 id="6-编译环境的问题"><a href="#6-编译环境的问题" class="headerlink" title="6 编译环境的问题"></a>6 编译环境的问题</h3><p><code>Go</code>语言的编译环境也可能遇到一些奇怪的问题:有的因为设置环境变量<code>GOOS</code>、<code>GOARCH</code>等参数影响编译输出,有的因为引入<code>Cgo</code>而对相关内容有依赖。</p><p>所以,编译环境一定要尽量简单、采用原始的操作系统,相关依赖(如环境变量)用命令行的方式输入,而不要隐藏在机器环境中。从这一点看,用<code>Docker</code>容器的优势就非常明显了。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>随着公司基础平台的完善,<strong>保障排查问题时的代码一致性</strong> 这个问题会越来越简单,但依然会发生问题。</p><p>通过这个话题,希望大家对自己的程序有进一步了解:你的代码是怎么发布到线上的?这个过程中会有什么坑?</p><h2 id="编程思考-在日常开发工作中融入项目管理思维"><a href="#编程思考-在日常开发工作中融入项目管理思维" class="headerlink" title="编程思考 - 在日常开发工作中融入项目管理思维"></a>编程思考 - 在日常开发工作中融入项目管理思维</h2><p>目前,许多公司里的专职“项目经理”角色越来越少,往往仅在大型项目中才能看到。</p><p>导致这个现象的原因,并不是项目管理不重要;相反地,而是项目管理的重要性已经深入到每个开发者的心中,人人都应该是自己的项目经理。在小型项目中,往往会由资深的技术负责人兼任项目管理的角色。</p><p>作为普通开发者,我们在日常开发中,其实已经接触到项目管理的一些内容了,如:</p><ul><li><strong>项目范围管理</strong> - 确认需求交付的范围</li><li><strong>项目成本管理</strong> - 估算需求投入的人力和时间</li><li><strong>项目进度管理</strong> - 统计与汇报当前的需求进度</li><li><strong>项目风险管理</strong> - 遇到风险点时,向领导沟通或采用备用方案</li></ul><p>这些相关的工作串联起来,就是一个简单的项目管理,只是不够体系化。当有合作开发的机会时,我们可以多观察其他经验丰富的同事或专职的项目经理,学习他们的项目管理方式,选择性地采用。</p><h2 id="工作生活-给生活穿插一些挑战"><a href="#工作生活-给生活穿插一些挑战" class="headerlink" title="工作生活 - 给生活穿插一些挑战"></a>工作生活 - 给生活穿插一些挑战</h2><p>我和绝大多数人一样,倾向于“躺平式”的生活;但我也知道,生活充满变化,过度躺平在未来抗风险能力会很差。在面对高度不确定性的未来时,我很容易陷入焦虑。</p><p>为了增强自己面对“危机”的能力,我会给自己的生活增加一些挑战,如:</p><ul><li>写一篇长文</li><li>跑一次半马</li><li>考一个证书</li></ul><p>这些目标很明确、难度不高,也不会要求自己做得多好。对我个人来说,这些挑战能增强自己面对挑战时的精神“肌肉”,就已达到目标了。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week12</title>
<link href="http://example.com/2023/03/19/weekly/weekly-2023-12/"/>
<id>http://example.com/2023/03/19/weekly/weekly-2023-12/</id>
<published>2023-03-19T10:00:00.000Z</published>
<updated>2023-03-20T14:03:54.952Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-经典命令行工具库cobra"><a href="#Go技巧-经典命令行工具库cobra" class="headerlink" title="Go技巧 - 经典命令行工具库cobra"></a>Go技巧 - 经典命令行工具库cobra</h2><h3 id="cobra简介"><a href="#cobra简介" class="headerlink" title="cobra简介"></a>cobra简介</h3><p><code>Go</code>程序的主流交付的形式为2种:服务端程序与命令行工具。服务端程序一般长期运行在服务器上,而命令行工具则往往是一次性运行的,又被称为<code>CLI</code>,即<code>client</code>客户端的简写。</p><p><code>CLI</code>工具的开发不难,但要做到方便易用,类似于<code>Kubernetes</code>的<code>kubectl</code>,还是非常考验开发能力的。今天,我介绍一款开发命令行程序的利器 - <a href="https://github.com/spf13/cobra">cobra</a>。</p><p><code>cobra</code>的安装命令如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> get -u github.com/spf13/cobra@latest</span><br></pre></td></tr></table></figure><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p><code>cobra init</code>可以快速地初始化一个程序:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 初始化 go module</span></span><br><span class="line">go mod init github.com/Junedayday/cobra-demo</span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 初始化cobra命令行工具,包括作者和License</span></span><br><span class="line">cobra init --author "junedayday@gmail.com" --license apache</span><br></pre></td></tr></table></figure><p>接下来,我会从结构与功能两个层面,对<code>cobra</code>进行介绍:</p><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><h4 id="新增子命令"><a href="#新增子命令" class="headerlink" title="新增子命令"></a>新增子命令</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 新增子命令 create和delete</span></span><br><span class="line">cobra add create</span><br><span class="line">cobra add delete</span><br></pre></td></tr></table></figure><p>在运行完上述两个命令后,我们编译一下程序,尝试着运行一下这个工具:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[root@localhost] ./cobra-demo -h</span><br><span class="line">Available Commands:</span><br><span class="line"> completion Generate the autocompletion script for the specified shell</span><br><span class="line"> create A brief description of your command</span><br><span class="line"> delete A brief description of your command</span><br><span class="line"> help Help about any command</span><br><span class="line"></span><br><span class="line">[root@localhost] ./cobra-demo create</span><br><span class="line">create called</span><br><span class="line"></span><br><span class="line">[root@localhost] ./cobra-demo delete</span><br><span class="line">delete called</span><br></pre></td></tr></table></figure><p>相关的提示已经自动添加了。而整体的代码架构也很清晰:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- cmd/</span><br><span class="line"> - create.go</span><br><span class="line"> - delete.go</span><br><span class="line"> - root.go</span><br><span class="line">- main.go </span><br></pre></td></tr></table></figure><h4 id="新增多级子命令"><a href="#新增多级子命令" class="headerlink" title="新增多级子命令"></a>新增多级子命令</h4><p>当工具变得复杂时,多级命令的可读性会比较棒,<code>cobra</code>也支持这个功能。假设我们要对上述的 <code>cobra-demo create</code>增加一种<code>mock</code>机制,即和正常创建方式区分开来,那么我们就可以运行以下命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> p即parent,对应的父命令</span></span><br><span class="line">cobra add mock -p=createCmd</span><br></pre></td></tr></table></figure><p>整个文件目录就成了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- cmd/</span><br><span class="line"> - create.go</span><br><span class="line"> - delete.go</span><br><span class="line"> - mock.go</span><br><span class="line"> - root.go</span><br><span class="line">- main.go </span><br></pre></td></tr></table></figure><p>重新编译后,命令行工具运行<code>create</code>时多了一个子命令提示:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@localhost] ./cobra-demo create</span><br><span class="line">Available Commands:</span><br><span class="line"> mock A brief description of your command</span><br></pre></td></tr></table></figure><h3 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h3><h4 id="参数解析"><a href="#参数解析" class="headerlink" title="参数解析"></a>参数解析</h4><p><code>cobra</code>工具在生成时,支持对每一个命令进行参数解析。官方在生成代码时提供了示例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> (</span><br><span class="line">foo *<span class="keyword">string</span></span><br><span class="line">toggle *<span class="keyword">bool</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line">rootCmd.AddCommand(createCmd)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Here you will define your flags and configuration settings.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Cobra supports Persistent Flags which will work for this command</span></span><br><span class="line"><span class="comment">// and all subcommands, e.g.:</span></span><br><span class="line">foo = createCmd.PersistentFlags().String(<span class="string">"foo"</span>, <span class="string">""</span>, <span class="string">"A help for foo"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Cobra supports local flags which will only run when this command</span></span><br><span class="line"><span class="comment">// is called directly, e.g.:</span></span><br><span class="line">toggle = createCmd.Flags().BoolP(<span class="string">"toggle"</span>, <span class="string">"t"</span>, <span class="literal">false</span>, <span class="string">"Help message for toggle"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的注释写得很清楚,参数分为2类:</p><ul><li><code>Persistent Flags</code> - 对该命令以及它的所有子命令生效</li><li><code>Local Flags</code> - 仅对该命令生效</li></ul><p>在复杂命令行工具的开发中,<code>Flag</code>经常会变得很多、很乱,我有两点建议:</p><ol><li><strong>用增加子命令的方式来减少入参的数量</strong>:参数越多,往往会让使用变得复杂性;而子命令相对而言更容易理解</li><li><strong>特殊的功能单独开放一个子命令</strong>:隔离复杂度</li></ol><h4 id="Command钩子"><a href="#Command钩子" class="headerlink" title="Command钩子"></a>Command钩子</h4><p><code>cobra</code>最核心的能力都放在结构体<code>cobra.Command</code>里,内部包含了诸多可自定义的能力,如简写、数据校验、版本等,而最核心的运行逻辑则是为以下5步(按顺序执行):</p><ul><li>PersistentPreRun()</li><li>PreRun()</li><li>Run()</li><li>PostRun()</li><li>PersistentPostRun()</li></ul><p>如果想要支持返回<code>error</code>,则将<code>XXXRun()</code>调整为<code>XXXRunE()</code>即可。<code>cobra</code>是一个开放的、普适性的工具,我们在使用它的特性时需要节制,才能保证后续的可维护性,例如:</p><ul><li>如果有复杂的业务功能,独立到另一个<code>package</code>中单独维护;</li><li>如果有数据一致性的要求,就尽量维护在一个命令中操作,如<code>Run</code>;</li></ul><p>一般建议在<code>cobra</code>命令层做的只做三件事:<strong>提高命令的可读性</strong>、<strong>解析参数</strong> 与 <strong>校验参数的基本格式</strong>。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p><code>cobra</code>工具是帮助开发者构建<code>CLI</code>程序的一款利器,核心优势在于能大幅度地提升使用者的体验(尤其是结合了Terminal中命令自动填充、美化等扩展能力)。</p><p>而在复杂项目中,则应提前考虑分层设计:<strong>隔离<code>cobra</code>生成代码与人工编写的业务代码</strong> - 前者是命令式地执行,后者更需要抽象。</p><h2 id="编程思考-以终为始的思维方式"><a href="#编程思考-以终为始的思维方式" class="headerlink" title="编程思考 - 以终为始的思维方式"></a>编程思考 - 以终为始的思维方式</h2><p><strong>以终为始</strong>,初听像是一个只停留于方法论的词汇,但它所代表的思维方式是非常有意义的。</p><p>以接口的两种风格为例:<strong>命令式</strong> 像是指挥者,必须很清楚内部的各项实现,然后指挥系统一步一步执行;而<strong>声明式</strong>则是告诉平台自己所期望的最终状态,而不关心其内部具体是怎么实现的。</p><p>命令式固然是一个很棒的解决方案,可以明确地指导我们达成目标,但过程中往往会遇到各种瓶颈,导致最终结果产生偏差;而声明式则是一种“以终为始”的解决方式,先明确自己的最终目标是什么,再逐步探索达到目标的路径。也许两者最终的效果一致,但“以终为始”的方式能让我们更聚焦于目标,减少过程中的损耗。</p><p>我们的开发工作也十分类似。过程中存在诸多的不确定性,未达预期是一种常态。以终为始的思路,能让我们减少复杂过程中的损耗。</p><h2 id="工作生活-资产配置"><a href="#工作生活-资产配置" class="headerlink" title="工作生活 - 资产配置"></a>工作生活 - 资产配置</h2><p>最近我开始接触理财相关的概念,其中印象最为深刻的是 <strong>4321原则</strong>,即</p><ul><li>40%投资</li><li>30%生活开销</li><li>20%储蓄备用</li><li>10%保险</li></ul><p>各位如果和我一样是个理财新手,不妨先按这种方式梳理一下个人或家庭的资产情况。这个理论提供的只是参考值,真正的执行可以按需调整,重点是要有长期的投资思想,如个人成长、房地产、股票,在人生的不同阶段有不同选择。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week11</title>
<link href="http://example.com/2023/03/12/weekly/weekly-2023-11/"/>
<id>http://example.com/2023/03/12/weekly/weekly-2023-11/</id>
<published>2023-03-12T10:00:00.000Z</published>
<updated>2023-03-14T11:43:09.436Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-Variable-Shadowing"><a href="#Go技巧-Variable-Shadowing" class="headerlink" title="Go技巧 - Variable Shadowing"></a>Go技巧 - Variable Shadowing</h2><p><code>Go</code> 语法中有一个较为隐蔽的特性 - <code>Variable Shadowing</code>,常被翻译为 <strong>变量隐藏</strong>。官方对这块提供的资料很少,经常有不少<code>Go</code>语言开发者在这块“踩雷”。</p><h3 id="语法意义-作用域"><a href="#语法意义-作用域" class="headerlink" title="语法意义 - 作用域"></a>语法意义 - 作用域</h3><p>既然<code>Go</code>官方支持这个特性,那就有对应的语法意义,简而言之就是 <strong>控制变量的作用域</strong>。</p><p>下面是一个简单的示例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">x := <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// value - 0</span></span><br><span class="line">fmt.Println(x)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="literal">true</span> {</span><br><span class="line"><span class="comment">// variable shadowing</span></span><br><span class="line">x := <span class="number">1</span></span><br><span class="line">x++</span><br><span class="line"><span class="comment">// value - 2</span></span><br><span class="line">fmt.Println(x)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// value - 0</span></span><br><span class="line">fmt.Println(x)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而其中的<code>x := 1</code>是创建了一个新的变量,它的作用域仅在 <code>if true</code>之后的括号内。当离开作用域之后,<code> x</code>变量又回到了原值。</p><h3 id="一个隐蔽的错误示例"><a href="#一个隐蔽的错误示例" class="headerlink" title="一个隐蔽的错误示例"></a>一个隐蔽的错误示例</h3><p>上述示例可以将 <code>x := 1</code> 修改为 <code>x = 1</code>,或者重新命名变量<code>x</code>,就可以避免引入 <code>variable shadowing</code> 。</p><p>但在实际开发中,常常出现一些隐蔽的case:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">f, err := foo()</span><br><span class="line">_ = f</span><br><span class="line"></span><br><span class="line"><span class="comment">// variable shadowing</span></span><br><span class="line"><span class="keyword">if</span> b, err := bar(); err == <span class="literal">nil</span> {</span><br><span class="line">_ = b</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// nil</span></span><br><span class="line">fmt.Println(err)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> {</span><br><span class="line"><span class="keyword">return</span> <span class="string">""</span>, <span class="literal">nil</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> {</span><br><span class="line"><span class="keyword">return</span> <span class="string">""</span>, errors.New(<span class="string">"bar"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在多变量返回时,很容易出现<code>variable shadowing</code>的情况。例如,上述示例中的<code>b, err := bar()</code> ,有两个返回值:</p><ul><li><code>b</code> ,前文未定义,作用域仅在对应的括号中,不会出现问题</li><li><code>err</code>,前文已定义,这里发生了<code>variable shadowing</code>,所以在括号内外是两个变量</li></ul><p>由于<code>Go</code>语言推荐将<code>error</code>作为最后一个返回值,所以往往在error这个变量上会发生<code>variable shadowing</code>,常被称为<code>error shadowing</code>。</p><p>尽管官方支持这种语法,但很容易发生预期不一致的结果。社区也提供了相关的工具<code>shadow</code>,可用下方的命令快速体验:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest</span><br><span class="line"><span class="meta">#</span><span class="bash"> 普通模式</span></span><br><span class="line">shadow ./...</span><br><span class="line"><span class="meta">#</span><span class="bash"> 严格模式</span></span><br><span class="line">shadow -strict ./...</span><br></pre></td></tr></table></figure><p>用工具可以发现问题,但如何解决呢?我推荐两个思路:</p><h3 id="思路一:移除-赋值使用"><a href="#思路一:移除-赋值使用" class="headerlink" title="思路一:移除:=赋值使用"></a>思路一:移除<code>:=</code>赋值使用</h3><p><code>variable shadowing</code>常出现的场景是多变量返回,并且同时存在已定义与未定义的变量。那么,我们可以尝试提前定义所需的变量,移除<code>:=</code>的赋值方式。例如将示例改造为:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> b <span class="keyword">string</span></span><br><span class="line"><span class="keyword">if</span> b, err = bar(); err == <span class="literal">nil</span> {</span><br><span class="line"> _ = b</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种提前定义的方式会增加代码量,但它不仅避免了<code>variable shadowing</code>,还有一个比较大的优势:<strong>提高可读性</strong>。</p><p>一行简单的变量定义,尤其是当定义的内容是一个复杂对象时,我们能从这个定义中读到很多内容,如一个<code>var fe foo.Example</code>,让变量简写<code>fe</code>和变量的定义<code>foo.Example</code>结合在一起,非常清晰。</p><h3 id="思路二:重命名"><a href="#思路二:重命名" class="headerlink" title="思路二:重命名"></a>思路二:重命名</h3><p>重命名时,我们对变量有两种处理方式,以下面的<code>errB</code>为例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> err error</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 变量仅被用于括号内,被丢弃</span></span><br><span class="line"><span class="keyword">if</span> b, errB := bar(); errB == <span class="literal">nil</span> {</span><br><span class="line"> _ = b</span><br><span class="line"> _ = errB</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 变量被用于括号内外,赋值给外部变量err</span></span><br><span class="line"><span class="keyword">if</span> b, errB := bar(); errB == <span class="literal">nil</span> {</span><br><span class="line"> _ = b</span><br><span class="line"> err = errB</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="个人推荐方案"><a href="#个人推荐方案" class="headerlink" title="个人推荐方案"></a>个人推荐方案</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 变量的生命周期 - 仅在括号内</span></span><br><span class="line"><span class="comment">// 重命名</span></span><br><span class="line"><span class="keyword">if</span> b, errB := bar(); errB == <span class="literal">nil</span> {</span><br><span class="line"> _ = b</span><br><span class="line"> _ = errB</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 变量的生命周期 - 括号内外</span></span><br><span class="line"><span class="comment">// 移除:=</span></span><br><span class="line"><span class="keyword">var</span> b <span class="keyword">string</span></span><br><span class="line"><span class="keyword">if</span> b, err = bar(); err == <span class="literal">nil</span> {</span><br><span class="line"> _ = b</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上是我个人的一种编程习惯,能减少因 <code>variable shadowing</code> 发生错误的概率,希望能帮助到各位同学。</p><h2 id="编程思考-表述不清晰,往往是因为掌握得不够深入"><a href="#编程思考-表述不清晰,往往是因为掌握得不够深入" class="headerlink" title="编程思考 - 表述不清晰,往往是因为掌握得不够深入"></a>编程思考 - 表述不清晰,往往是因为掌握得不够深入</h2><p>在写方案、查问题、沟通业务时,开发者们经常有种感觉 - 我已经表达得很清晰了,但别人常常无法听懂。</p><p>我也经常遇到这种情况。以前,我会觉得是对方的问题,如对相关领域的知识储备太少;而现在,我更倾向于是自己对这块内容的掌握度仍不够,导致无法通俗易懂地表达。</p><p>我的思维主要有两点转变:</p><ol><li>大师能面向不同人群、通俗易懂地讲清深奥的知识;</li><li>改变优先从自身出发,而不是寄希望于他人的主动变化;</li></ol><p>当收到他人对你表述不清晰的评价时,不要过于抵触,不妨趁这个机会再深入研究相关知识点,提升自己的水平。</p><h2 id="工作生活-你看到的,是你想看到的"><a href="#工作生活-你看到的,是你想看到的" class="headerlink" title="工作生活 - 你看到的,是你想看到的"></a>工作生活 - 你看到的,是你想看到的</h2><p>最近读完了《拆掉了思维里的墙》,我印象最深的一句话是:<strong>你看到的,是你想看到的</strong>。</p><p>这个世界可能是客观的,但每个人的世界都是主观的。在事前或者事后,很多人都清楚该以什么样的态度面对世界,理论上很完美;但事到临头,面对带有负面内容的事物,我们往往会不自觉地以自己最习惯的方式来抵抗、躲避。</p><p>如果你想看到一个自己理想中的世界,往往需要一定的锻炼,形成“肌肉记忆”,让自己的心态变得更强壮。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week10</title>
<link href="http://example.com/2023/03/05/weekly/weekly-2023-10/"/>
<id>http://example.com/2023/03/05/weekly/weekly-2023-10/</id>
<published>2023-03-05T10:00:00.000Z</published>
<updated>2023-03-05T13:26:16.104Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>进入3月,天气逐渐变得暖和。</p><p>趁现在,出门走走,感受一下清风与阳光,给心灵一段轻松的休憩。</p><span id="more"></span><h2 id="Go技巧-我是如何做Code-Review的"><a href="#Go技巧-我是如何做Code-Review的" class="headerlink" title="Go技巧 - 我是如何做Code Review的?"></a>Go技巧 - 我是如何做Code Review的?</h2><p>如何做Code Review,这个话题过大,容易出现很多争议性的内容。为了缩小讨论的范围,我今天分享的内容主要源于个人的经验,适用于多种后端语言。</p><h3 id="CR的最终目标是什么?"><a href="#CR的最终目标是什么?" class="headerlink" title="CR的最终目标是什么?"></a>CR的最终目标是什么?</h3><p>我先抛出自己的观点,CR的最终目标分两个方向:</p><ul><li><strong>技术上,保证代码的可维护性</strong></li><li><strong>业务上,保证系统功能的正确性</strong></li></ul><p>第一点相对容易做到,毕竟代码风格很容易直接看出来。不过代码是否能保证可维护性,还是很考验团队技术负责人的水平,这里就不展开了。</p><p>不少人对第二点会存在争议:CR的reviewer往往不了解这个需求的背景与技术实现,在有限的时间里,如何保证系统功能的正确性?诚然,系统功能的正确性是极难解决的问题,别说CR,连单测、联调、验收、甚至上线后都可能频繁出现问题,靠一个“纸上谈兵”的CR能解决什么?</p><p>功能的正确性很难保障是编程圈的常态,导致CR不断被人诟病 - 麻烦又没小姑偶。但是,我们也可以观察到有很多追求技术的团队与个人分享了各自的实践,或多或少带来了帮助。</p><p>下面,我来谈谈个人的三个实践,简单来说就是 <strong>总 - 分 - 总</strong>。</p><blockquote><p>以下的内容不适用于部分维护遗留系统的团队,投入与产出不成比例。</p><p>但从个人来说,可以按照下面的思路CR自己提交的代码,是一个提升个人能力的有效手段。</p></blockquote><h3 id="梳理功能:从出入口串联逻辑"><a href="#梳理功能:从出入口串联逻辑" class="headerlink" title="梳理功能:从出入口串联逻辑"></a>梳理功能:从出入口串联逻辑</h3><p>做一个功能的开发,出入口是最好把控整体的方法。以一个微服务程序为例,主要出入口分为3种:</p><ol><li>服务本身的接口</li><li>数据库层的定义</li><li>调用第三方服务的接口</li></ol><p>如果能结合分层,将1放在最上层,2、3放在最下层,整体的代码就清晰很多。这时,Reviewer的重心可以分这三步:</p><ol><li><strong>先看最上层接口的数据定义</strong>:根据出入的数据定义,可快速了解接口要做什么</li><li><strong>再看业务层的技术方案</strong>:业务层为分两种情况<ol><li>CRUD类,没有什么复杂业务逻辑,可不写方案,直接读代码就能看懂</li><li>复杂业务类,重点看技术方案中的核心设计(缺少技术方案时,就要求<strong>把核心业务逻辑写在一个或几个重点函数中,注释详尽</strong>)</li></ol></li><li><strong>最后看下层的数据库与RPC调用</strong>:通过了解系统怎么与外部交互的,完善对整个功能的理解</li></ol><h3 id="细节打磨:从分层特性打磨代码"><a href="#细节打磨:从分层特性打磨代码" class="headerlink" title="细节打磨:从分层特性打磨代码"></a>细节打磨:从分层特性打磨代码</h3><p>串联了整个逻辑,只代表了我们大致了解了逻辑,距离技术上的代码可维护性和业务上的功能正确性都还有相当一段路。接下来,我会从分层的维度去打磨代码。</p><p>接口层的代码是整个服务的门面,我重点关注两块。首先是<strong>数据定义适宜</strong> - 既不缺失关键字段导致无法使用,又没有冗余字段来提升调用方的理解成本,这点比较考验经验,是一个不断优化的过程;其次,我非常重视<strong>接口文档的正确性</strong> - 让代码实现与接口文档保证一致,这部分往往会结合框架进行自动化。</p><p>业务层相关的代码是服务的核心,风格因团队、框架而异,但我建议以一个实践为核心:<strong>可测试性</strong>。业务变化很快,可测试性是重要保障的一个门槛。为了提升可测试性,我们应先考虑对相关代码进行优化,例如抽象、拆分、mock等手段,再去写相关的单元测试。</p><p>最下层的数据库与RPC调用相关的代码一般都有ORM或SDK的封装,功能上复杂度不高,但仍是CR的重点之一:<strong>优先关注边界问题</strong>,即通过数据库或第三服务是不是一个最优解,毕竟一旦涉及到第三方系统,后续排查的复杂度就会越来越大;其次要<strong>关注性能</strong>,如数据库的索引字段,外部服务的接口性能等。</p><h3 id="掌控全局:再次俯瞰整个功能"><a href="#掌控全局:再次俯瞰整个功能" class="headerlink" title="掌控全局:再次俯瞰整个功能"></a>掌控全局:再次俯瞰整个功能</h3><p>打磨完了细节,这时再从整体功能的角度来看这部分代码,往往能找到很多更重要的优化项,例如:</p><ul><li>优化命名</li><li>抽象合理</li><li>日志齐全</li><li>超时处理</li></ul><p>这一阶段是保障功能正确的最重要机会,是对以上两个阶段的“厚积薄发”。我个人尤其注重这一阶段。</p><h2 id="编程思考-珍惜工作中的决策机会"><a href="#编程思考-珍惜工作中的决策机会" class="headerlink" title="编程思考 - 珍惜工作中的决策机会"></a>编程思考 - 珍惜工作中的决策机会</h2><p>最近有同学在工作一段时间后,发现个人的能力遇到了瓶颈,找我聊了聊。在我看来,他们的瓶颈往往是由于个人的认知非常受限,突破瓶颈的一个有效手段就是有足够的决策机会。</p><p>举个例子,如果你是维护某个模块的工程师,对这部分的功能已经了如指掌,这时你需要有一个机会:如果让你重新设计这个模块、或者规划更大的系统,这就能让你的能力有一次突破。</p><p>决策小到一个技术方案,大到一个系统设计,这里面需要关注两方面:</p><ol><li>自身能力的不断提高</li><li>在团队中树立足够的信誉</li></ol><p>不少同学往往忽略了第二点,工作中总抱着一种怀才不遇的心态,而团队领导则认为他好高骛远。理论与实践总是存在偏差,我们要从每个小的机会入手,逐步证明自己,才能在更大的舞台上展现自己。</p><h2 id="工作生活-10公里慢跑"><a href="#工作生活-10公里慢跑" class="headerlink" title="工作生活 - 10公里慢跑"></a>工作生活 - 10公里慢跑</h2><p>近一个月,我已经养成了每周一次10公里慢跑的习惯。</p><p>奔跑的过程中,最困难的是3~5公里,我会不断产生各种念头劝自己放弃;最后几公里反而是最顺畅的,因为我的目标很明确、只要再坚持跑几步就能达到。</p><p>一次长跑,也是一个对心性磨炼的过程。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>进入3月,天气逐渐变得暖和。</p>
<p>趁现在,出门走走,感受一下清风与阳光,给心灵一段轻松的休憩。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week9</title>
<link href="http://example.com/2023/02/26/weekly/weekly-2023-9/"/>
<id>http://example.com/2023/02/26/weekly/weekly-2023-9/</id>
<published>2023-02-26T10:00:00.000Z</published>
<updated>2023-02-27T14:05:47.825Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><span id="more"></span><h2 id="Go技巧-快速学习官方库ast"><a href="#Go技巧-快速学习官方库ast" class="headerlink" title="Go技巧 - 快速学习官方库ast"></a>Go技巧 - 快速学习官方库<code>ast</code></h2><p>当我们谈及<code>Go</code>语言底层时,往往会聊<code>GMP</code>相关的并发原理,或者是以<code>reflect</code>为代表的反射处理,它们也是面试中的常客。</p><p>而我今天想推荐的一个底层库 - <code>ast</code>,全名抽象语法树(abstract syntax tree),不仅能让我们进一步掌握<code>Go</code>的基础语法,更是一个开发<strong>标准化</strong>和<strong>提效</strong>工具的关键技能。</p><h3 id="AST的基本概念"><a href="#AST的基本概念" class="headerlink" title="AST的基本概念"></a>AST的基本概念</h3><p><code>ast</code>的官方概念理解起来比较复杂,有兴趣的可以参考<a href="https://zhuanlan.zhihu.com/p/28516587">这篇知乎</a>,或者阅读更专业的资料。</p><p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/20230224211609.png"></p><p>对初学者来说,抓住上图中的两个关键因素:</p><ol><li>树 - 了解树相关的深度遍历算法</li><li>节点 - 各语法特征,如变量、条件语句等</li></ol><h3 id="Go的AST节点类型"><a href="#Go的AST节点类型" class="headerlink" title="Go的AST节点类型"></a>Go的AST节点类型</h3><p>学习<code>ast</code>,从它底层的数据定义入手会让我们事半功倍。</p><p><code>ast</code>语法的核心抽象是<code>Node</code>,定义如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Node <span class="keyword">interface</span> {</span><br><span class="line">Pos() token.Pos <span class="comment">// 起始定义的位置</span></span><br><span class="line">End() token.Pos <span class="comment">// 结束定义的位置</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>它的作用就是解析出对应语法的位置。整个<code>Node</code>相关的接口与实现比较复杂,我以网上的一个版本为例:</p><p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/ast-tree.jpeg"></p><p>整个语法树很庞杂,每个接口与结构体都有自己的一些特点。为了方便大家加深这部分的理解,我们从一个具体的case入手</p><h3 id="AST的示例代码"><a href="#AST的示例代码" class="headerlink" title="AST的示例代码"></a>AST的示例代码</h3><p>示例代码如下,即解析本身main.go文件,打印出import的库与类型定义。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"go/ast"</span></span><br><span class="line"><span class="string">"go/parser"</span></span><br><span class="line"><span class="string">"go/token"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"><span class="comment">// 定义解析的具体文件</span></span><br><span class="line">fSet := token.NewFileSet()</span><br><span class="line">f, err := parser.ParseFile(fSet, <span class="string">"main.go"</span>, <span class="literal">nil</span>, parser.ParseComments)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 怎么解析 - 用visit对象去Walk(遍历)对应文件</span></span><br><span class="line">visit := &MyVisitor{}</span><br><span class="line">ast.Walk(visit, f)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> MyVisitor <span class="keyword">struct</span>{}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 具体处理的函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(vi *MyVisitor)</span> <span class="title">Visit</span><span class="params">(node ast.Node)</span> <span class="title">ast</span>.<span class="title">Visitor</span></span> {</span><br><span class="line"><span class="keyword">switch</span> node.(<span class="keyword">type</span>) {</span><br><span class="line"><span class="keyword">case</span> *ast.ImportSpec:</span><br><span class="line"><span class="comment">// import库</span></span><br><span class="line">fmt.Println(node.(*ast.ImportSpec).Path.Value)</span><br><span class="line"><span class="keyword">case</span> *ast.TypeSpec:</span><br><span class="line"><span class="comment">// 类型定义</span></span><br><span class="line">fmt.Println(node.(*ast.TypeSpec).Name.Name)</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> vi</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其余语法的使用,大家可以参考前面图中的<code>Node</code>接口与实现的定义,对照着实现。</p><p>为了加深大家对<code>ast</code>这棵树的理解,我们再细化一下上面的例子。</p><h3 id="AST的两种处理思路"><a href="#AST的两种处理思路" class="headerlink" title="AST的两种处理思路"></a>AST的两种处理思路</h3><p>示例中的<code>import</code>代码,即</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"go/ast"</span></span><br><span class="line"><span class="string">"go/parser"</span></span><br><span class="line"><span class="string">"go/token"</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>它对应的<code>ast</code>中的结构体是</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> GenDecl <span class="keyword">struct</span> {</span><br><span class="line">Doc *CommentGroup <span class="comment">// associated documentation; or nil</span></span><br><span class="line">TokPos token.Pos <span class="comment">// position of Tok</span></span><br><span class="line">Tok token.Token <span class="comment">// IMPORT, CONST, TYPE, or VAR</span></span><br><span class="line">Lparen token.Pos <span class="comment">// position of '(', if any</span></span><br><span class="line">Specs []Spec</span><br><span class="line">Rparen token.Pos <span class="comment">// position of ')', if any</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而每个import选项,则是</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 抽象</span></span><br><span class="line"><span class="keyword">type</span> Spec <span class="keyword">interface</span> {</span><br><span class="line">Node</span><br><span class="line">specNode()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现</span></span><br><span class="line"><span class="keyword">type</span> ImportSpec <span class="keyword">struct</span> {</span><br><span class="line">Doc *CommentGroup <span class="comment">// associated documentation; or nil</span></span><br><span class="line">Name *Ident <span class="comment">// local package name (including "."); or nil</span></span><br><span class="line">Path *BasicLit <span class="comment">// import path</span></span><br><span class="line">Comment *CommentGroup <span class="comment">// line comments; or nil</span></span><br><span class="line">EndPos token.Pos <span class="comment">// end of spec (overrides Path.Pos if nonzero)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以,我们可以有两种方式来访问到每个<code>ImportSpec</code>:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(vi *MyVisitor)</span> <span class="title">Visit</span><span class="params">(node ast.Node)</span> <span class="title">ast</span>.<span class="title">Visitor</span></span> {</span><br><span class="line"><span class="keyword">switch</span> node.(<span class="keyword">type</span>) {</span><br><span class="line"><span class="comment">// 方法1 - 直接处理下层的叶子节点</span></span><br><span class="line"><span class="keyword">case</span> *ast.ImportSpec:</span><br><span class="line"><span class="comment">// import库</span></span><br><span class="line">fmt.Println(node.(*ast.ImportSpec).Path.Value)</span><br><span class="line"><span class="comment">// 方法2 - 先解析出上层节点,再处理下层节点</span></span><br><span class="line"><span class="keyword">case</span> *ast.GenDecl:</span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> node.(*ast.GenDecl).Specs {</span><br><span class="line"><span class="keyword">switch</span> v.(<span class="keyword">type</span>) {</span><br><span class="line"><span class="keyword">case</span> *ast.ImportSpec:</span><br><span class="line">fmt.Println(v.(*ast.ImportSpec).Path.Value)</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> vi</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在用<code>ast</code>编写相关工具时,我建议优先按思路2去编写代码,它的优势在于两点:</p><ol><li><strong>扩大上下文信息</strong> - 除了基本的<code>ImportSpec</code>,还可以使用父节点<code>GenDecl</code>中的共用信息</li><li><strong>代码可读性高</strong> - 先处理父节点,解析到具体的结构体,再进行各个子节点的处理,思路很自然</li></ol><p>当整个<code>Visit</code>处理的内容比较多时,就需要进行一定的拆分,用<strong>递归</strong>来减少复杂度。</p><h3 id="AST在日常工作中的使用示例"><a href="#AST在日常工作中的使用示例" class="headerlink" title="AST在日常工作中的使用示例"></a>AST在日常工作中的使用示例</h3><p><code>ast</code>的特性看起来很酷,但很少会直接应用在项目的代码里。</p><p>不过,作为官方支持、可用来分析<code>Go</code>代码的库,它常用于制作二进制工具,在不同的场景使用:</p><ol><li><strong>进阶性的代码规范性检查</strong> - 如检查某层代码的import情况,保证分层规范</li><li><strong>自定义的代码生成</strong> - 如根据注释自动生成定义,根据方法生成mock接口</li><li><strong>编译前统一对库或方法的替换</strong> - 在编译前,对某些特定的库或方法进行替换,修改原<code>go</code>文件</li></ol><p>在优秀的框架中,<code>ast</code>往往与标准化相辅相成,形成正反馈:<strong>代码标准化的程度越高,<code>ast</code>就越能提升自动化、保障质量;<code>ast</code>应用得越广泛,代码的标准化程度自然就越高</strong>。</p><h2 id="编程思考-业务也是技术人员的核心能力"><a href="#编程思考-业务也是技术人员的核心能力" class="headerlink" title="编程思考 - 业务也是技术人员的核心能力"></a>编程思考 - 业务也是技术人员的核心能力</h2><p>最近,为了项目的推进,我和大量的开发人员进行了沟通,发现部分新人对“业务”的认知有明显偏差。</p><p>技术有两个极致的方向:</p><ol><li>底层理论性的研究</li><li>面向业务的trade-off</li></ol><p>前者是为技术领域开辟新的领域,只有极少的研究员会参与这类工作;而面向业务的trade-off则是大多数开发者能接触到的终极目标,难点往往在于:</p><ol><li>技术储备广 - 有多样的解决方案,各有利弊</li><li>业务认知深 - 洞察业务的核心价值</li><li>技术 ✖️ 业务 - 两者结合时的决策能力</li></ol><p><strong>技术是开发者的立身之本,而业务是公司的立身之本。</strong> 优秀的开发者并不在于能想出100个技术方案,而是能提出3个各有利弊的关键技术方案、并且能根据业务情况给出自己的意见。</p><h2 id="工作生活-如果无法理解,也请包容他人"><a href="#工作生活-如果无法理解,也请包容他人" class="headerlink" title="工作生活 - 如果无法理解,也请包容他人"></a>工作生活 - 如果无法理解,也请包容他人</h2><p>我有段时间心理洁癖非常严重:我会尝试各种方法、各种角度去理解一些人,但如果用尽方法、仍无法理解对方的所作所为,我就会对其非常反感(当然,这样的人是极少数的)。</p><p>理解是一个很理想的方式,能让我更深刻地了解对方,也更适合深度、长期的关系。但在现实生活中,我们很难投入那么多的时间与情绪去熟悉每个人,也因不可避免的个人认知偏差导致误会。</p><p>所以,在和大部分人相处时,包容是一种让自己更轻松的方式。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week8</title>
<link href="http://example.com/2023/02/19/weekly/weekly-2023-8/"/>
<id>http://example.com/2023/02/19/weekly/weekly-2023-8/</id>
<published>2023-02-19T10:00:00.000Z</published>
<updated>2023-02-21T13:16:52.242Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>这段时间,我在尝试着远离抖音、B站、头条等消息媒介。</p><p>这些平台承载了过多大数据推荐算法,反复向人们推送着高度重复性的内容,脱离了获取信息的初衷 - <strong>扩大自己的认知面</strong>。如果你关注的内容包括了失业等负面内容,还容易加强你的焦虑感。</p><span id="more"></span><h2 id="Go技巧-结构体的赋值小技巧"><a href="#Go技巧-结构体的赋值小技巧" class="headerlink" title="Go技巧 - 结构体的赋值小技巧"></a>Go技巧 - 结构体的赋值小技巧</h2><p>在日常的CRUD开发过程中,我们往往会用到一些结构体:它们只是一些简单的数据的组合,很难用对象来封装。比如说,有一个订单表,里面包括20+个field。当我们创建时,就需要对这些field赋值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Order <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// 包括近20个字段</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面,我们来看三种常见的方法,并分析各自的利弊:</p><h3 id="方案1-全量透传结构体"><a href="#方案1-全量透传结构体" class="headerlink" title="方案1-全量透传结构体"></a>方案1-全量透传结构体</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数据从http body中解析出来</span></span><br><span class="line"><span class="keyword">var</span> b []<span class="keyword">byte</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析到order</span></span><br><span class="line"><span class="keyword">var</span> order = <span class="built_in">new</span>(Order)</span><br><span class="line">json.Unmarshal(b, &order)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建到数据库</span></span><br><span class="line">db.Insert(order)</span><br></pre></td></tr></table></figure><p>这种思路确实非常方便,但仅限于轻量级项目。</p><p>如果业务逻辑稍微复杂点,就会出现无法支持的情况。问题的根本在于:<strong>在程序的不同层面,结构体各有各的使用场景,像传输效率、安全、性能等</strong>。</p><blockquote><p>最经典的莫过于时间类型的参数:在数据库保存为<code>timestamp</code>格式,而在接口层往往是<code>YYYY-MM-DD hh:mm:ss</code>或<code>int64</code>类型,无法用透传实现。</p></blockquote><h3 id="方案2-用一个通用库解决问题"><a href="#方案2-用一个通用库解决问题" class="headerlink" title="方案2-用一个通用库解决问题"></a>方案2-用一个通用库解决问题</h3><p>既然我们要从一个结构体转成另一个结构体,就自然产生了一个想法 - 写个通用库。</p><p>常见的实现思路有2种:序列化工具或发射。这里以序列化工具为例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 源结构体,来自http</span></span><br><span class="line"><span class="keyword">type</span> OrderDTO <span class="keyword">struct</span>{}</span><br><span class="line"><span class="comment">// 目标结构体,保存到MySQL</span></span><br><span class="line"><span class="keyword">type</span> OrderDao <span class="keyword">struct</span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> order *orderDTO</span><br><span class="line">b, err := json.Marshal(order)</span><br><span class="line"><span class="keyword">var</span> target = <span class="built_in">new</span>(OrderDao)</span><br><span class="line">err = json.Unmarshal(b, target)</span><br></pre></td></tr></table></figure><p>这种思路在编写代码时确实方便,但是,长期来看有两大隐患:</p><ol><li>中间操作的性能可能成为性能瓶颈</li><li>两个结构体不一定能保证一致,如数据库里叫<code>name</code>,但接口则叫<code>order_name</code></li></ol><p>尤其是第二点,使得通用库变得不再通用,就会出现一些tricky的实现,比如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Order <span class="keyword">struct</span> {</span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`json:"order_name"`</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种方式很容易在未来成为一个埋雷点。随着通用库的封装,埋雷越来越多,往往就变得不可维护了。</p><h3 id="方案3-傻瓜式赋值"><a href="#方案3-傻瓜式赋值" class="headerlink" title="方案3-傻瓜式赋值"></a>方案3-傻瓜式赋值</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">transfer</span><span class="params">(from *orderDTO)</span> <span class="params">(to *OrderDao)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> OrderDao {</span><br><span class="line"> Id: from.Id,</span><br><span class="line"> Name: from.OrderName,</span><br><span class="line"> CreateTime: from.CreateTime.Format(<span class="string">"2006-01-02 15:04:05"</span>),</span><br><span class="line"> Cost: from.EndTime.Sub(from.StartTime).Seconds(),</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>这样的代码写起来很累,但对阅读者来说是非常方便的(往往封装成一个函数,放在文件末尾即可)。在开发与排查问题的过程中,它有三个优点无可比拟:</p><ol><li><strong>填充逻辑清晰</strong>:数据源来自哪个字段,又被填充到了哪个字段,一目了然</li><li><strong>灵活自定义加工</strong>:如代码中时间<code>CreateTime</code>的格式化,以及耗时<code>Cost</code>的逻辑计算</li><li><strong>兼容性与安全性高</strong>:两个结构体的变更,要么不影响这个转换逻辑(兼容性高,如新增字段),要么就会编译报错(安全性高,如修改相关字段的名称或类型)</li></ol><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>在我开发<code>Go</code>语言的近六年时间里,看到相关的代码风格远不止这3种。通过反反复复倒腾,我认为代码可维护性最为重要,所以更倾向于 <strong>傻瓜式赋值</strong> 这个方法,哪怕牺牲一部分效率也是值得的(在IDE的帮助下,其实效率几乎没有影响)。</p><p>你的想法又是怎么样呢?欢迎与我交流沟通。</p><h2 id="编程思考-重视技术评审的过程性价值"><a href="#编程思考-重视技术评审的过程性价值" class="headerlink" title="编程思考 - 重视技术评审的过程性价值"></a>编程思考 - 重视技术评审的过程性价值</h2><p>我们模拟个场景:你写了一个自认为 <strong>当下最优解的技术方案</strong>,信心满满地拿出来评审,结果遭到各方的挑战,无法回答清楚很多问题,心里忿忿不平;评审后仍按你的方案执行,结果出来也很不错,你就觉得当时提问的那些评委都不如你。</p><p>相信很多人都有这样的经历,我也相当一段时间处于这种状态。不过,现在的我更愿意静下心,去关注过程里的一些内容:</p><ul><li>评委提出的“无意义”问题,是否有一部分来源于我叙述的不清楚?尤其是背景</li><li>评委们关心的角度是否与我的视角有差异?比如我更关心代码实现,而评委关注的是业务、稳定性、安全等</li><li>你获得的成果,是否仍有改进空间?适当地收集一些评委意见,弥补个人视角的盲区</li></ul><p>技术评审的主要价值是 <strong>优化方案</strong>。作为评审方,大部分的评委在多方面的能力都是高于本人的。如果你抱着“评委就是跟我过不去、就是乱挑刺”的想法去面对他们,那么对你来说就是浪费时间。</p><p>如果真有这么一个瞎挑刺的评委、他又是你工作中绕不过的领导,那该怎么办呢?退而求其次,技术评审还提供了一个交流的机会,你可以尝试着摸清他的一些思路,想想如何与其相处,<strong>为自己营造更轻松的工作环境</strong>。</p><h2 id="工作生活-主动创造美好瞬间"><a href="#工作生活-主动创造美好瞬间" class="headerlink" title="工作生活 - 主动创造美好瞬间"></a>工作生活 - 主动创造美好瞬间</h2><p>这个小标题起得有点煽情。</p><p>我想表达的是:在年轻时,我们可以肆意地享受人生的美好。但随着时间推移,我们往往会被家庭琐事、工作压力、身体健康等问题困扰,这就需要我们从被动的享受、慢慢地转变为主动创造,这样就能大幅提高生活的幸福感。</p><p>而且,我们创造的美好瞬间能让家人与朋友同样感受到快乐,提高整个圈子的幸福感;而整体氛围的提升,往往能形成正反馈,收益人也包括了你。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>这段时间,我在尝试着远离抖音、B站、头条等消息媒介。</p>
<p>这些平台承载了过多大数据推荐算法,反复向人们推送着高度重复性的内容,脱离了获取信息的初衷 - <strong>扩大自己的认知面</strong>。如果你关注的内容包括了失业等负面内容,还容易加强你的焦虑感。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week7</title>
<link href="http://example.com/2023/02/12/weekly/weekly-2023-7/"/>
<id>http://example.com/2023/02/12/weekly/weekly-2023-7/</id>
<published>2023-02-12T10:00:00.000Z</published>
<updated>2023-02-15T04:51:58.773Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>本周的总结发得比较晚,花在打磨编程思考部分的时间比较多。</p><p>虽然最终版本也不是很满意,不过也算达到了阶段性交付的水平。</p><span id="more"></span><h2 id="Go技巧-并发代码的单元测试"><a href="#Go技巧-并发代码的单元测试" class="headerlink" title="Go技巧 - 并发代码的单元测试"></a>Go技巧 - 并发代码的单元测试</h2><p>在<code>Go</code>语言开发的过程中,我们或多或少会引入并发模式,常见的如<code>go</code>、<code>channel</code>、<code>sync.WaitGroup</code>等。这些并发原语使用起来很方便,但常常会阻碍相关代码的单元测试,如依赖的<code>channel</code>发生阻塞,<code>mutex</code>被锁等,导致想验证的重要代码根本跑不到。</p><p>这里,我介绍一下自己的三个心得,用一句话概括,<strong>合理利用分层进行拆分,屏蔽并发逻辑</strong>。</p><blockquote><p>没有分层的基础或工具库不在本次的讨论范围内,但也可以借鉴这里的思想。</p></blockquote><h3 id="多协程的逻辑交由上层控制"><a href="#多协程的逻辑交由上层控制" class="headerlink" title="多协程的逻辑交由上层控制"></a>多协程的逻辑交由上层控制</h3><p>如下,原先的业务逻辑代码包括了两块处理逻辑:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="comment">// 逻辑A</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="comment">// 逻辑B</span></span><br><span class="line"> }()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于逻辑B相关的代码无法验证,所以这个单元测试能做的很有限。这时,通过分层,我们将代码拆分为两部分:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 上层代码 - 使用基础的并发特性,没有必要做单元测试</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span> {</span><br><span class="line"> FooA()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">go</span> FooB()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下层代码 - 做严格的单元测试</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FooA</span><span class="params">()</span></span> {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FooB</span><span class="params">()</span></span> {</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此时的单元测试就变得直观了。</p><h3 id="锁类逻辑由下层对象封装"><a href="#锁类逻辑由下层对象封装" class="headerlink" title="锁类逻辑由下层对象封装"></a>锁类逻辑由下层对象封装</h3><p>业务代码常常会包含锁,这就导致很多函数中有大量的<code>Lock</code>、<code>UnLock</code>操作,容易在单元测试里验证一些逻辑时发生死锁。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// do a</span></span><br><span class="line"> m.Lock()</span><br><span class="line"> <span class="keyword">defer</span> m.UnLock()</span><br><span class="line"> <span class="comment">// do b</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从抽象层面来说,业务逻辑的核心代码尽量减少锁这种 <strong>底层的并发原语</strong>,把它们放在业务逻辑里也很影响阅读体验。这时,将锁划分到下层会让代码逻辑更清晰:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下层对象</span></span><br><span class="line"><span class="keyword">type</span> FooA <span class="keyword">struct</span> {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> FooB <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// 锁</span></span><br><span class="line"> sync.Mutex</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 业务代码</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 这里不包含锁逻辑</span></span><br><span class="line"> fooA.Do()</span><br><span class="line"> <span class="comment">// 这里包含锁逻辑</span></span><br><span class="line"> fooB.DO()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从使用锁的角度来说,<code>Lock</code>与<code>UnLock</code>之间的逻辑应尽量短,所以很适合放在底层对象、交由它自行控制,也能缩短锁的影响范围。</p><h3 id="同层抽离出核心控制函数"><a href="#同层抽离出核心控制函数" class="headerlink" title="同层抽离出核心控制函数"></a>同层抽离出核心控制函数</h3><p>在<code>Go</code>语言中,有一些并发特性、尤其以<code>channel</code>为代表,很难通过分层解决。例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> c <span class="keyword">chan</span> <span class="keyword">int</span></span><br><span class="line"> <span class="comment">// 发送消息</span></span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="comment">// send to c</span></span><br><span class="line"> }()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 接收消息</span></span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="comment">// receive from c</span></span><br><span class="line"> }()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的<code>channel</code>更多的是一种具有业务特性的并发控制,单独抽出一个 <strong>核心控制函数</strong> 就能提升整体的可读性与可测试性:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 核心控制函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Foo</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> c <span class="keyword">chan</span> <span class="keyword">int</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 发送逻辑</span></span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> a := logicA()</span><br><span class="line"> c <- a</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 接收逻辑</span></span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> a := <- c</span><br><span class="line"> logicB(a)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以下逻辑均不包含channel,方便单元测试</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">logicA</span><span class="params">()</span> <span class="title">int</span></span> {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">logicB</span><span class="params">(a <span class="keyword">int</span>)</span></span> {</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>核心控制函数很难通过单元测试完整验证,更考验的是开发人员对<code>Go</code>并发编程的基本功。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>以上三点是我在工作过程中的经验心得,帮助我解决了很多并发代码中的单元测试问题。它们不一定是最佳实践,但希望能对各位遇到相关问题时有一些启发。</p><h2 id="编程思考-平台开发的三个阶段"><a href="#编程思考-平台开发的三个阶段" class="headerlink" title="编程思考 - 平台开发的三个阶段"></a>编程思考 - 平台开发的三个阶段</h2><p>如今, <strong>平台开发</strong> 这个词已经广泛应用在程序员圈子内。在我看来,相关的开发者可以分为三个阶段:</p><ol><li><strong>初级阶段</strong>:根据用户的需求明细写过程性的CRUD,实现功能</li><li><strong>中级阶段</strong>:以<code>OpenAPI</code>的方式向多业务、多用户提供能力</li><li><strong>高级阶段</strong>:定制核心能力+复用通用能力</li></ol><p>其中,很多人往往走到中级阶段后就阻塞不前了,并且心得意满、认为平台的已经处于最终形态。但是,平台如果长期处于中级阶段,相关弊端会随着时间推移越发凸显,例如:</p><ul><li>没有技术壁垒 - 越是通用,越是普通,很容易被开源产品取代</li><li>调用方的使用成本高 - 开放接口往往透传各类参数,使用方成本高</li><li>无法贴合一线业务创造价值 - 平台以甲方自居,不去理解业务的使用场景</li></ul><p>我见过许多工作了近10年仍处于中级阶段的工程师,他们的技术能力十分高超,但很难在职业发展的道路上再进一步,关键就是在于对平台的认知 - <strong>平台的核心价值是靠 为业务创造的价值 来间接体现的,而不是靠 平台自身的能力 来直接评价</strong>。</p><h2 id="工作生活-少给自己找借口的机会"><a href="#工作生活-少给自己找借口的机会" class="headerlink" title="工作生活 - 少给自己找借口的机会"></a>工作生活 - 少给自己找借口的机会</h2><p>年后,我完整地跑了2次十公里,过程中也有近10次跑了不到一半就放弃。我的跑步配速不快,理应每次都能达成目标,那为什么还有这么多次半途而废呢?</p><p>我回顾了这几次的经历,总结如下:<strong>一旦我想要中途放弃,借口总是能找到的;所以,在整个跑步的过程中,保持一个稳定、平和的心态,不要让放弃的想法有可趁之机。</strong> </p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>本周的总结发得比较晚,花在打磨编程思考部分的时间比较多。</p>
<p>虽然最终版本也不是很满意,不过也算达到了阶段性交付的水平。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week6</title>
<link href="http://example.com/2023/02/05/weekly/weekly-2023-6/"/>
<id>http://example.com/2023/02/05/weekly/weekly-2023-6/</id>
<published>2023-02-05T10:00:00.000Z</published>
<updated>2023-02-07T02:10:36.455Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>本周开始,绝大多数人的工作与生活步入了常规模式。</p><p>希望大家的身体与心理都能尽快地适应工作或学习节奏,保持一颗愉悦的心情。</p><span id="more"></span><h2 id="Go技巧-浅谈1-20版本特性"><a href="#Go技巧-浅谈1-20版本特性" class="headerlink" title="Go技巧 - 浅谈1.20版本特性"></a>Go技巧 - 浅谈1.20版本特性</h2><p>本周<code>Go</code>语言<a href="https://tip.golang.org/doc/go1.20">1.20版本正式发布</a>,我们对其重要特性进行简单分析,为后续掌握<code>Go</code>的发展脉络做好铺垫。</p><h3 id="切片转数组"><a href="#切片转数组" class="headerlink" title="切片转数组"></a>切片转数组</h3><p>在1.17版本时,切换转数组的<a href="https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_or_array_pointer">语法</a>已经支持,只是写起来比较冗余。在这次1.20版本进行了优化:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x []<span class="keyword">byte</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 更早版本 - 手工填充</span></span><br><span class="line"><span class="keyword">var</span> s = [<span class="number">4</span>]<span class="keyword">byte</span>{x[<span class="number">0</span>],x[<span class="number">1</span>],x[<span class="number">2</span>],x[<span class="number">3</span>]}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Go1.17 - 指针操作</span></span><br><span class="line"><span class="keyword">var</span> s = *(*[<span class="number">4</span>]<span class="keyword">byte</span>)(x)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Go1.20 - 直接类型转换</span></span><br><span class="line"><span class="keyword">var</span> s = [<span class="number">4</span>]<span class="keyword">byte</span>(x)</span><br></pre></td></tr></table></figure><p>这个语法本身没有太多的学习成本,但官方在此时才完全支持,这就导致:<strong>在1.17版本前的绝大多数开发场景下,<code>Go</code>开发者都更愿意用更灵活的切片来代替数组</strong>。但相较于切片,固定长度的数组对节约内存更为友好。</p><p>而随着1.20版本的推出,使用数组的语法便利性大幅提高,相信会有更多追求极致性能的人开始引入数组。</p><h3 id="unsafe三个新函数"><a href="#unsafe三个新函数" class="headerlink" title="unsafe三个新函数"></a>unsafe三个新函数</h3><p>unsafe中提供了三个新函数:<code>SliceData</code>、<code>String</code>和<code>StringData</code>。</p><p>这三个函数是低频使用的,我们按需阅读<a href="https://pkg.go.dev/unsafe">官方文档</a>即可。如果你对<strong>指针</strong>的相关知识基础扎实,很容易理解这三个函数。</p><h3 id="时间常量的定义"><a href="#时间常量的定义" class="headerlink" title="时间常量的定义"></a>时间常量的定义</h3><p>终于,在1.20版本引入了开发者最常用的<a href="https://pkg.go.dev/time#pkg-constants">时间戳格式</a>:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> DateTime = <span class="string">"2006-01-02 15:04:05"</span></span><br></pre></td></tr></table></figure><p>经历了多少次手写时间,查找了多少个相关的低级bug,官方终于把这个时间格式放到了标准库中。强烈建议升级到1.20版本的项目,先做一次变量的全局替换。</p><p>这里分享一个小tip:如何记忆这个时间戳?记住1~6:</p><ul><li>1月</li><li>2日</li><li>下午3点</li><li>4分</li><li>5秒</li><li>06年</li></ul><h3 id="error的组合"><a href="#error的组合" class="headerlink" title="error的组合"></a>error的组合</h3><p>标准库中的<code>error</code>支持了组合的能力,扩展了错误的使用场景。</p><p>例如,程序发生了错误,从开发者来看,既有属于数据库的错误A,又有属于业务逻辑的错误B。在上层用<code>errors.Is</code>判定时,只能在A与B中二选一。而1.20则引入了<code>Join</code>类型,则可以将两个错误组合起来,上层判断时两个都成立。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> errA error</span><br><span class="line"><span class="keyword">var</span> errB error</span><br><span class="line"></span><br><span class="line"><span class="comment">// 组合错误</span></span><br><span class="line"><span class="keyword">var</span> err = errors.Join(errA, errB)</span><br></pre></td></tr></table></figure><h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><ul><li>更新<strong>结构体比较</strong>的规范的说明(严格来说是修正了不严谨的说明,代码已经实现)</li><li>泛型类型对<code>Comparable</code>更多的支持</li><li><code>Go</code>后续支持各操作系统的计划</li><li>相关工具链的优化</li><li>编译速度提升,达到差不多引入泛型前的水平</li><li>运行时提升CPU效率约2%</li></ul><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>从近几个版本的迭代来看,<code>Go</code>官方一直关注前后版本兼容性,这对开发者来说体验是很棒的。官方把主要精力花在了两个方面:</p><ol><li>相关工具与库的建设,完善基本开发的生态</li><li>底层能力(如编译)的建设</li></ol><p>可见,<code>Go</code>团队通过前期的设计,帮助我们屏蔽了下层的细节,是一个很棒的编程体验。</p><h2 id="编程思考-对成长来说,划分边界是为了扩大边界"><a href="#编程思考-对成长来说,划分边界是为了扩大边界" class="headerlink" title="编程思考 - 对成长来说,划分边界是为了扩大边界"></a>编程思考 - 对成长来说,划分边界是为了扩大边界</h2><p>在编程开发中,有许多划分边界的事情,如:</p><ol><li>代码的模块划分</li><li>微服务的划分</li><li>业务领域的划分</li><li>网络的分层</li></ol><p>划分边界后就有了分工,优秀的分工协作可以带来组织效率的提升。但是,随着日复一日的重复性工作,很容易就只专注于自己职责范围内的部分,而忽略了相关方。近几年,许多公司倡导的 “跳出当前角色”、“共赢思想”、“FT-Feature Team” 等词汇,都是一种跨出当前工作内容、与周边其余组织团队合作的思想。</p><p>我们要从更高的视角来理解: 划分边界是为了整体实现更高价值的一种方法,而不是成为一种阻碍。所以,我们要不断地跨出当前职责,比如:</p><ul><li>了解某个模块的依赖方 - 如学习ORM库时,顺便了解一下数据库的基本知识</li><li>主动了解周边服务 - 如做支付系统时,了解订单系统的设计与交互</li></ul><p>从许多大厂的职级定义来看,职级的提升往往就是边界的扩大 - 从单个服务、一个系统、整套业务,最后甚至要有跳出技术的视角。</p><h2 id="工作生活-学习一些营养学知识"><a href="#工作生活-学习一些营养学知识" class="headerlink" title="工作生活 - 学习一些营养学知识"></a>工作生活 - 学习一些营养学知识</h2><p>最近看了<a href="https://book.douban.com/subject/27590675/">《你是你吃出来的》</a>和<a href="https://book.douban.com/subject/35340053/">《你是你吃出来的2》</a>两本书。书的内容涉及到许多健康、医学相关的内容,作者的文笔已经很通俗易懂了,有兴趣的朋友可以自己去看看。</p><blockquote><p>对书中的例子要保持理性:</p><p>比如某患者听了作者的说法,换了食谱,三个月后就恢复了。但是,三个月内其余因素是否有变化?比如吃了药、加强锻炼、心态变化等。</p></blockquote><p>读完本书,我有个观点一直萦绕心头:高血压、糖尿病等这种亚健康的情况,是很难定义边界的。虽然业内有参考值,但很容易因人种、环境等因素而变化,颇为个性化。而我们称它们为“病”,往往是已经严重到远超临界值了。对于这些情况,吃药就像是亡羊补牢 - 虽然有效果,但很难根治。我相信,大众更需要的是营养学知识的普及。</p><p>目前,绝大多数人很难有专属的家庭医生,这就要求每个人都拥有基础的营养学知识储备。相关的理论知识已经很完善了,但当前社会缺少一个权威的宣传路径,把这块知识普及到大众。</p><p>个人觉得像公众号、抖音视频等,由于整个传播路径上干扰信息过多,但很难具有公众说服力。而类似于 <strong>国家反诈中心</strong> 这种独立app更具有权威效应。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>本周开始,绝大多数人的工作与生活步入了常规模式。</p>
<p>希望大家的身体与心理都能尽快地适应工作或学习节奏,保持一颗愉悦的心情。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week5</title>
<link href="http://example.com/2023/01/29/weekly/weekly-2023-5/"/>
<id>http://example.com/2023/01/29/weekly/weekly-2023-5/</id>
<published>2023-01-29T10:00:00.000Z</published>
<updated>2023-02-07T01:47:49.755Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>本周过后,绝大多数打工人就需要从休假模式切换到工作模式。</p><p>趁着春节假期的尾巴,我们聊三个轻松的话题。</p><span id="more"></span><h2 id="Go技巧-从官方问卷谈谈未来发展"><a href="#Go技巧-从官方问卷谈谈未来发展" class="headerlink" title="Go技巧 - 从官方问卷谈谈未来发展"></a>Go技巧 - 从官方问卷谈谈未来发展</h2><p><code>Go</code>官方最近开放了一个<a href="https://google.qualtrics.com/jfe/form/SV_bNnbAtFZ0vfRTH8?s=b">问卷</a>,里面收集了用户的相关意见。整个问卷是全英文的,全部填完需要15min左右,有很多是收集用户背景与满意度的常规问题。</p><p>这里,我对其中的关键信息做一下提炼。</p><h3 id="配套工具的两个开发方向-Module与IDE"><a href="#配套工具的两个开发方向-Module与IDE" class="headerlink" title="配套工具的两个开发方向 - Module与IDE"></a>配套工具的两个开发方向 - Module与IDE</h3><p>官方在工具侧的两大投入方向在 <strong>模块化</strong> 与 <strong>IDE配套</strong> 。</p><p>先说说模块化。自<code>Go Module</code>推出之后,已成为一套官方标准,但仍有不少缺憾:如多模块化管理、互相依赖问题,需要官方给出明确的指导意见。而IDE配套,最常见的包括<code>Goland</code>、<code>VsCode</code>、<code>Vim</code>,需要有更多的插件进行支持,如接口与实现的跳转、重构相关工具等。</p><p>在我看来,IDE配套工具比模块化更为重要。尽管<code>Goland</code>已支撑了不少能力,但是其高收费、吃内存的特性,对开发者很不友好,而相对轻量级的<code>VsCode</code>面对复杂项目时,各项能力很难支撑。而模块化的问题虽然重要,但目前已有临时的解决方案,只要关注官方推出的方案即可。</p><h3 id="用Go语言开发的领域"><a href="#用Go语言开发的领域" class="headerlink" title="用Go语言开发的领域"></a>用Go语言开发的领域</h3><p>问卷中的领域方向罗列如下:</p><ul><li>Games</li><li>Mobile apps</li><li>Libraries or frameworks</li><li>Agents and daemons (e.g., monitoring)</li><li>Automation/scripts (e.g., deployment, configuration management)</li><li>A runnable/interactive program (CLI)</li><li>Desktop / GUI applications</li><li>Embedded devices / Internet of Things</li><li>Websites / web services (returning HTML)</li><li>Machine learning / Artificial intelligence</li><li>API/RPC services (returning non-HTML)</li><li>Data processing (e.g., pipelines, aggregation)</li></ul><p>我们不妨思考一下,<strong>Go语言适合哪些方向</strong>?(官方在问卷后面征询了用户意见,希望<code>Go</code>语言支持哪些方向)</p><p>这里,其实大部分的方向都具有 <strong>门槛</strong>,并不适合<code>Go</code>语言,例如:</p><ul><li>游戏方向需要引擎基础,主流是<code>C++</code></li><li>移动设备上的应用注重体验,要用原生的语言</li><li>嵌入设备很吃性能,主流是<code>C/C++</code></li><li>机器学习有TensorFlow/PyTorch等框架,主流是<code>Python</code></li><li>大数据生态基本定型,主流是<code>Java</code></li></ul><p>所以,目前用到<code>Go</code>语言的场景主要是三块:</p><ul><li>基础库与框架</li><li>命令行工具(采集agent、交互CLI)</li><li>后端服务(包括返回HTML与普通RPC)</li></ul><p>对这三个领域,我有如下建议,希望能引起思考:</p><ol><li>基础库与框架<ol><li>入门:基础库与框架的最重要目标是提效,那么如何量化到具体指标呢?</li><li>进阶:<code>Go</code>开源社区有不少库与框架,该如何取长补短、又能形成自己的技术壁垒呢?</li></ol></li><li>命令行工具<ol><li>入门:<code>Go</code>语言工具与脚本(<code>Shell</code>/<code>Python</code>)等的优劣比较,如何选型?</li><li>进阶:云原生技术生态结合,找准工具的切入角度与定位</li></ol></li><li>后端服务<ol><li>入门:与主流编程语言(<code>Java</code>)、框架(<code>Spring</code>)的优劣比较,如何选型?</li><li>进阶:沉淀一整套用<code>Go</code>语言开发的方法论(基建、迭代流程、技术价值等)</li></ol></li></ol><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>从目前来看,<code>Go</code>语言自身的迭代频率不高,整个生态也没有具有突破性的新产品诞生(如<code>Kubernetes</code>),接下来很难有井喷式的增长。而另一方面,<code>Go</code>语言入门的门槛较低,很难形成一定的壁垒(如果希望单靠编程语言就能具有技术壁垒的,建议走C++的路)。</p><p>所以,想要有足够的市场竞争力,除了<code>Go</code>语言,我们还需要 <strong>综合培养多方面的技能</strong>:如计算机基础、业务理解、项目管理等,而不要固守一项技能,被市场慢慢淘汰。</p><h2 id="编程思考-不要陷入过程性的编码"><a href="#编程思考-不要陷入过程性的编码" class="headerlink" title="编程思考 - 不要陷入过程性的编码"></a>编程思考 - 不要陷入过程性的编码</h2><p>这个子标题包含两层意思。</p><p>第一层比较直观:<strong>不要单纯面向过程地编码,而是学会抽象、面向对象</strong>。这是一个老生常谈的话题,我举一个例子:</p><p>现在,要开发一个管理书本的功能。在面向过程时,我们思路是增删改查,很容易写出对应的代码;而如果要抽象,我们需要钻研 - <strong>书</strong> 这个对象,就冒出各种问题:</p><ul><li>书需要哪些属性?</li><li>书的管理有权限吗?</li><li>书与书之间有关联吗?</li></ul><p>这些问题也许最终并没有对代码开发有所帮助,但能帮助开发者锻炼抽象思维与理解业务场景。</p><p>第二层则是一种工作状态:<strong>不要闷头写代码,而应时不时确认方向的正确性。</strong></p><p>在Coding时,有些人会陷入 <strong>心流</strong> 的状态,感觉如有神助,一下子写出几千行代码,但回过头却发现这些代码是无效的 - 不满足需求。要解决这个问题,需要突破两个舒适区:</p><ol><li><strong>高频沟通</strong>:以文档或demo的方式与需求方沟通,而不要闷头“自嗨式”地闭门造车</li><li><strong>放弃沉没成本</strong>:在软件行业高频迭代的场景下,很容易出现某项工作中途叫停的情况。如果有足够的自信支撑,那么继续坚持是一种勇气;而如果判断最终大概率失败,那么选择中途放弃也是很大的勇气。</li></ol><p>用一个词来总结以上两点的话,就是常谈的 <strong>以终为始</strong> 。</p><h2 id="工作生活-更多维的视角"><a href="#工作生活-更多维的视角" class="headerlink" title="工作生活 - 更多维的视角"></a>工作生活 - 更多维的视角</h2><p>在春节的尾巴,我和朋友去吃了个自助餐。</p><p>入座后,我发现隔壁座有个小哥哥,穿着一身睡衣,一个人不紧不慢地吃着,时不时地和服务员闲聊两句,整个就餐过程表现得非常轻松;而到我这边,则一心想着“吃回本”,填鸭式地往嘴里塞,就餐过程非常仓促,最后挺着撑饱的肚子才离开。</p><p>我的这种情况在网上非常常见,尤其是那些大胃王的短视频,给观众带来价值观是一种 <strong>零和博弈</strong> - 吃得少就是商家赚、食客亏,吃得多就是食客赚、商家亏。这种观点没有问题,却扭曲了很多人吃自助餐的初衷 - 吃自助只是为了不受拘束地点餐或者享受某个美食。如果顾客只是为了吃亏商家,那么商家要么降低服务品质,要么只能倒闭。</p><p>世界并不是非黑即白的,我们要从更多维的视角来看问题:单维度的视角(如金钱的得失)很容易发生冲突,而多维度的视角(比如那位小哥哥享受了就餐的过程)可以让我们更享受生活。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>本周过后,绝大多数打工人就需要从休假模式切换到工作模式。</p>
<p>趁着春节假期的尾巴,我们聊三个轻松的话题。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week4</title>
<link href="http://example.com/2023/01/22/weekly/weekly-2023-4/"/>
<id>http://example.com/2023/01/22/weekly/weekly-2023-4/</id>
<published>2023-01-22T12:00:00.000Z</published>
<updated>2023-01-24T15:03:30.620Z</updated>
<content type="html">< error {</span><br><span class="line"> err := mysql.Insert(order)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> err</span><br><span class="line"> }</span><br><span class="line"> b,err := json.Marshal(order)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> err</span><br><span class="line"> }</span><br><span class="line"> fmt.Println(<span class="keyword">string</span>(b))</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 三个Order的Create方法就非常清晰了</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(o *Order1)</span> <span class="title">Create</span><span class="params">()</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="keyword">var</span> order *OrderInfo1</span><br><span class="line"> <span class="keyword">return</span> Create[OrderInfo1](order)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(o *Order2)</span> <span class="title">Create</span><span class="params">()</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="keyword">var</span> order *OrderInfo2</span><br><span class="line"> <span class="keyword">return</span> Create[OrderInfo2](order)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(o *Order3)</span> <span class="title">Create</span><span class="params">()</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="keyword">var</span> order *OrderInfo3</span><br><span class="line"> <span class="keyword">return</span> Create[OrderInfo3](order)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>泛型特性的引入,往往出现在数据处理层,即和基础库、工具库相关的地方,而在业务层很少出现。我们可以从如下两点进行分析:</p><ul><li>业务层主要的特点在与 <strong>逻辑差异大</strong>,对数据结构也有各种校验等,不适用泛型;</li><li>数据处理层则往往逻辑一致,仅仅只有数据结构的差异,泛型非常适配。</li></ul><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>函数复用、嵌套+<code>Overwrite</code>、泛型,是三种非常有效的代码复用技巧。希望大家能够循序渐进,在工程中找到属于自己的最佳实践。</p><h2 id="编程思考-开发前的三个文档"><a href="#编程思考-开发前的三个文档" class="headerlink" title="编程思考 - 开发前的三个文档"></a>编程思考 - 开发前的三个文档</h2><p>在开发一个项目前,有三个文档是必备的,我们称为 - <strong>BRD</strong>、<strong>PRD</strong>、<strong>技术方案</strong>,它们在项目流程中依次编写。</p><ol><li>BRD(商业需求文档):这个文档有一个关键词 - 商业价值,不仅要了解用户痛点,更要结合市场,发掘价值</li><li>PRD(产品需求文档):与产品经理角色相关,设计功能交互,体现出两种重要的思维:产品思维与用户思维</li><li>技术方案:开发者最熟悉的文档,最主要的是设计,但更重要的是评估能力,如排期、风险</li></ol><p>编写技术方案不难,普通开发者工作两三年就能有一个很棒的呈现;而PRD则须要 <strong>视野转换</strong>,从用户与产品的角度来思考功能的开发;BRD则最为复杂,往往要多年行业经验积累以及深刻的用户洞察。</p><p>大家可以在日常开发中多主动地接触优秀的PRD、BRD,不仅能拓宽视野,更能提升个人认知。</p><h2 id="工作生活-学会聚焦,才能做好取舍"><a href="#工作生活-学会聚焦,才能做好取舍" class="headerlink" title="工作生活 - 学会聚焦,才能做好取舍"></a>工作生活 - 学会聚焦,才能做好取舍</h2><p>不同人、在不同的阶段,对工作和生活的平衡点都有不同的理解。所以,我认为没有必要去过多地从他人经验里去探求 <strong>最佳平衡点</strong>,也没有必要把工作和生活当作对立面,而是在日常反复问自己:<strong>我究竟想要什么?</strong></p><p>有取,往往就需要舍弃,这时就会犹豫代价是否过大。我总是过多地担忧所失去的,就扭曲了原问题:<strong>不再关注自己最想要的,而转过头去关注可能失去的,情绪上出现焦虑,甚至恐慌</strong>。简而言之,就是要认清自己,学会聚焦。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>新的一年已经到来,祝各位读者2023年身体健康、家庭美满。</p>
<p>回到本篇的主题,我继续来聊聊本周的一些心得。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week3</title>
<link href="http://example.com/2023/01/15/weekly/weekly-2023-3/"/>
<id>http://example.com/2023/01/15/weekly/weekly-2023-3/</id>
<published>2023-01-15T12:00:00.000Z</published>
<updated>2023-01-16T02:52:25.866Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>本周,我的工作模式正式从远程办公回到了现场办公。恰逢过年,整体工作节奏放缓,切换的过程很顺畅。</p><p>虽然我十分期待远程办公成为常态,但不得不承认,这种模式在中国落地,还有一段路要走。</p><span id="more"></span><h2 id="Go技巧-提高ORM使用体验的三个要点"><a href="#Go技巧-提高ORM使用体验的三个要点" class="headerlink" title="Go技巧 - 提高ORM使用体验的三个要点"></a>Go技巧 - 提高ORM使用体验的三个要点</h2><p>ORM是一个非常高频使用的开发工具。以下图为例,<strong>Go程序内与MySQL中,数据存储是异构的</strong> ,这就导致传统开发方式会分成两步:</p><ol><li>将Go程序中的数据转换成MySQL的 <code>SQL</code> 语句</li><li>解析MySQL 返回的数据到具体结构体中</li></ol><p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/ORM.png"></p><p>这部分的开发有大量重复性的代码,如拼接SQL、数据解析,所以就有了ORM这个概念 - <strong>将内存中的数据结构(对象)与数据库中的表对应起来</strong>。一旦映射关系建立,那就可以调用ORM里的CRUD完成日常开发。在Go语言程序中,最常见的就是<a href="https://gorm.io/zh_CN/docs/">gorm</a>。</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>我们以Book作为对象为例,它在Go程序中的定义是:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Book <span class="keyword">struct</span> {</span><br><span class="line"> Id <span class="keyword">int64</span> <span class="string">`gorm:"column:id"`</span></span><br><span class="line"> BookName <span class="keyword">string</span> <span class="string">`gorm:"column:book_name"`</span></span><br><span class="line"> UpdateTime time.Time <span class="string">`gorm:"column:update_time"`</span></span><br><span class="line"> Status <span class="keyword">int</span> <span class="string">`gorm:"column:status"`</span> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对应MySQL中的建表语句为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `books`</span><br><span class="line">(</span><br><span class="line"> `id` <span class="type">bigint</span>(<span class="number">20</span>) UNSIGNED <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT COMMENT <span class="string">'主键'</span>,</span><br><span class="line"> `book_name` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">''</span> COMMENT <span class="string">'书名'</span>,</span><br><span class="line"> `update_time` datetime <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> NOW() COMMENT <span class="string">'更新时间'</span>,</span><br><span class="line"> `status` tinyint(<span class="number">3</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">'状态'</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE <span class="operator">=</span> InnoDB</span><br><span class="line"> AUTO_INCREMENT <span class="operator">=</span> <span class="number">1</span></span><br><span class="line"> CHARSET <span class="operator">=</span> utf8mb4</span><br><span class="line"> <span class="keyword">COLLATE</span> <span class="operator">=</span> utf8mb4_bin COMMENT <span class="string">'书'</span>;</span><br></pre></td></tr></table></figure><p>完成定义后,我们可以使用<code>gorm</code>库实现CRUD了。但基于ORM库,开发中还是会高频出现一些奇怪的问题:<strong>明明程序没有bug,ORM的操作结果却没有达到预期</strong>。例如插入时<code>status</code>字段是0,没有报错,但查询时缺变成了100。</p><p>这类问题,往往是开发者在设计时没有注重 <strong>用户认知</strong> 导致的,也就是说 现象反直觉、所见非所得。我们今天的话题,将基于此展开:</p><h3 id="要点一:程序侧-节制地使用ORM能力"><a href="#要点一:程序侧-节制地使用ORM能力" class="headerlink" title="要点一:程序侧 - 节制地使用ORM能力"></a>要点一:程序侧 - 节制地使用ORM能力</h3><p>ORM往往扩展了很多能力,但大幅度地增加了用户的学习成本与排查问题时的成本。以<a href="https://gorm.io/zh_CN/docs/models.html#%E5%AD%97%E6%AE%B5%E7%BA%A7%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6">GORM字段权限控制</a>为例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> {</span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"<-:create"`</span> <span class="comment">// 允许读和创建</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"<-:update"`</span> <span class="comment">// 允许读和更新</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"<-"`</span> <span class="comment">// 允许读和写(创建和更新)</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"<-:false"`</span> <span class="comment">// 允许读,禁止写</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"->"`</span> <span class="comment">// 只读(除非有自定义配置,否则禁止写)</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"->;<-:create"`</span> <span class="comment">// 允许读和写</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"->:false;<-:create"`</span> <span class="comment">// 仅创建(禁止从 db 读)</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"-"`</span> <span class="comment">// 通过 struct 读写会忽略该字段</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"-:all"`</span> <span class="comment">// 通过 struct 读写、迁移会忽略该字段</span></span><br><span class="line"> Name <span class="keyword">string</span> <span class="string">`gorm:"-:migration"`</span> <span class="comment">// 通过 struct 迁移会忽略该字段</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看起来特性很酷,但如果你作为读代码的人,你愿意去读一个结构体中每个Field的<code>tag</code>详情吗?而且,这种限制藏得很隐蔽,发生问题后排查起来很累。</p><p>因此,程序侧的ORM定义,最重要的是能<strong>保证程序数据结构与数据库存储结构一一映射,其余特性需要慎用</strong>。</p><blockquote><p>慎用不代表不用。</p><p>如果能在团队内部形成规范,一方面这个规范能落地到代码里,另一方面也能宣传到各个成员、让大家形成共识,那就能用这些特性提升开发效率。</p></blockquote><h3 id="要点二:数据库侧-最简化设计"><a href="#要点二:数据库侧-最简化设计" class="headerlink" title="要点二:数据库侧 - 最简化设计"></a>要点二:数据库侧 - 最简化设计</h3><p>程序侧的代码对开发者可见,排查问题相对清晰。而如果问题最终是在数据库侧导致的,那么就变得复杂了:</p><ul><li>技术领域不同 - 数据库存在一定的专业性,经验尚浅的开发者需要一定的经验积累</li><li>访问权限 - 角色、环境等问题,可能导致排查困难</li><li>滞后性 - 出现问题的优先排查对象往往是代码,数据库往往会被我们“默认”认为没问题</li></ul><p>所以,我们在前期设计数据库侧的内容时,要尽可能地保证简单。我个人的评判标准是:<strong>让Go结构体的数据,和MySQL表中的一行数据完全对应</strong>,不做额外的工作。</p><p>我举两个反例:</p><ol><li>字段默认值有特殊的含义,如建表时<code>status</code>的默认值设置为100<ol><li>改进方案:如果100这个值有业务含义,应在Go程序中设置</li></ol></li><li>表中增加Trigger,如<code>status</code>字段修改为某个值后,自动触发另一个字段的修改<ol><li>改进方案:在Go程序中实现这块逻辑</li></ol></li></ol><h3 id="要点三:ORM能力与数据库特性的综合考量"><a href="#要点三:ORM能力与数据库特性的综合考量" class="headerlink" title="要点三:ORM能力与数据库特性的综合考量"></a>要点三:ORM能力与数据库特性的综合考量</h3><p>第三个要点最为复杂,它需要结合ORM库的具体能力以及数据库的自身特性来综合考量:ORM的有些特性并不完善,具体在哪实现?</p><p>依旧以gorm为例,在用<code>Book</code>结构体进行<a href="https://gorm.io/zh_CN/docs/update.html#%E6%9B%B4%E6%96%B0%E5%A4%9A%E5%88%97">多列更新</a>时,无法更新其中的默认值,如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 官方示例</span></span><br><span class="line"><span class="comment">// 代码原理:Active字段是默认值false,所以不会更新</span></span><br><span class="line"><span class="comment">// 用户认知:因为惯性思维,往往认为这个值会被设置为false</span></span><br><span class="line">db.Model(&user).Updates(User{Name: <span class="string">"hello"</span>, Age: <span class="number">18</span>, Active: <span class="literal">false</span>})</span><br></pre></td></tr></table></figure><p>我们先不考虑具体解决方案,而是希望大家能认识到ORM的局限性 - 想用一个结构体完全覆盖所有的增删改查场景,是不现实的。选择方案,其实是<code>trade-off</code>,选择一个团队更能快速理解的策略。</p><blockquote><p>想了解方案的同学,可以参考我之前的<a href="https://junes.tech/2022/11/03/go-study/go-rpc-4/">博客</a>。</p></blockquote><p>第三点是进阶性质的能力,需要大量ORM与数据库侧的开发经验,今天不作展开。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>ORM的使用体验会大幅提升CRUD的开发与维护效率。我比较提倡 <strong>在设计时,最简化ORM与数据库侧的特性</strong>,只采用其核心的映射能力。</p><p>而当简化到一定程度后,我们可以打通两侧的数据结构,如示例中的<code>Book</code>结构体与<code>books</code>建表语句。由于MySQL中的数据类型更为复杂,可以维护一个从 <strong>解析建表语句,自动生成Go中ORM结构体</strong> 的代码生成工具。</p><blockquote><p>实现可以参考<a href="https://junes.tech/2021/09/27/go-framework/go-framework-7/">博客</a> </p></blockquote><h2 id="编程思考-开发者的coding经验"><a href="#编程思考-开发者的coding经验" class="headerlink" title="编程思考 - 开发者的coding经验"></a>编程思考 - 开发者的coding经验</h2><p>如今的应届生在校或实习时就具备了颇为深厚的编程经验,参加工作后能快速地胜任日常需求,这就引起了老一批工程师的焦虑,不禁怀疑:我们的coding经验究竟有什么价值?</p><p>下面,我分享一下个人的思考,会从低到高三个维度进行讲述:</p><ol><li><strong>代码维度 - 写得好,读得懂</strong>:看过、写过的代码多,一方面让自己写代码时可读性提高,另一方面也能适应五花八门的项目风格。</li><li><strong>功能维度 - 懂需求,善取舍</strong>:代码所实现的功能,往往和最终预期有出入,如沟通损耗、认知差异等;而功能实现的过程中往往需要取舍,要理清主次先后。</li><li><strong>系统维度 - 识风险,促迭代</strong>:开发的代码从来就不是孤立的,需要识别出它对系统其余功能是否会产生风险;同时,本次开发也是一个迭代的机会,例如建设更通用的模块、修复一些历史包袱等。</li></ol><p>以上三点维度不同,但很难从价值维度区分高低。从这三点来看,一个资深coder对团队的价值非常重要。</p><h2 id="工作生活-焦虑感的缓解"><a href="#工作生活-焦虑感的缓解" class="headerlink" title="工作生活 - 焦虑感的缓解"></a>工作生活 - 焦虑感的缓解</h2><p>这几年,我的焦虑感与日俱增,尤其是近两年的行业低谷。面对焦虑,专家们有很多思路,这里分享三个对我帮助最大的方法:</p><ul><li>多锻炼,既能保证身体能量充沛,又可以释放很多负能量</li><li>多读书(尤其是心理学),提升心智成熟,坦然地面对不确定性</li><li>多沟通,与同事、领导、朋友等多种角色,进行真诚的交流</li></ul><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>本周,我的工作模式正式从远程办公回到了现场办公。恰逢过年,整体工作节奏放缓,切换的过程很顺畅。</p>
<p>虽然我十分期待远程办公成为常态,但不得不承认,这种模式在中国落地,还有一段路要走。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>【每周小结】2023-Week2</title>
<link href="http://example.com/2023/01/08/weekly/weekly-2023-2/"/>
<id>http://example.com/2023/01/08/weekly/weekly-2023-2/</id>
<published>2023-01-08T12:00:00.000Z</published>
<updated>2023-01-08T13:10:10.874Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p><p>作为一名年龄30+程序员,随着2023年的到来,我的工作和生活正在逐步变化。</p><p>本系列会以周维度为周期,记录我对如下三块内容的思考,大家挑选各自感兴趣的内容阅读即可:</p><ul><li><strong>Go技巧</strong> - Go开发者作为重点阅读的群体,不忘初心</li><li><strong>编程思考</strong> - 面向所有开发者,从系统设计等高层维度进行分享</li><li><strong>工作生活</strong> - 以更贴近生活的视角,分享时间管理、职业发展、焦虑感的一些心得</li></ul><span id="more"></span><h2 id="Go技巧-用接口interface提高模块间协作效率"><a href="#Go技巧-用接口interface提高模块间协作效率" class="headerlink" title="Go技巧 - 用接口interface提高模块间协作效率"></a>Go技巧 - 用接口<code>interface</code>提高模块间协作效率</h2><p>本周要分享的一个技巧是 - <strong>用接口<code>interface</code>提高模块间协作效率</strong>。</p><p>我们在协作开发时,在划分清边界后,就需要协同开发。而这个边界,如果能结合<code>interface</code>特性,就会大幅提升效率。从边界功能的角色来看,主要包括2个:<strong>提供方</strong>与<strong>调用方</strong>。</p><p>从下图来看:</p><p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/%E6%8E%A5%E5%8F%A3%E8%AE%BE%E8%AE%A1.png"></p><p>我们以一个具体工作内容为例:我们要开发一个Book的CRUD的工作,但时间紧迫,所以计划分为两块:</p><ul><li>A同学 - HTTP API部分</li><li>B同学 - MySQL数据库部分</li></ul><p>而由于数据库部分的工作比较少,所以计划让B同学来主导接口这块工作,即B是提供方,A是使用方。接下来的三块工作内容如下:</p><h3 id="1-设计接口"><a href="#1-设计接口" class="headerlink" title="1 - 设计接口"></a>1 - 设计接口</h3><p>B同学给出一版接口:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 核心接口定义</span></span><br><span class="line"><span class="keyword">type</span> BookDao <span class="keyword">interface</span> {</span><br><span class="line"> Create(book *Book) error</span><br><span class="line"> Delete(bookId <span class="keyword">int64</span>) error</span><br><span class="line"> List(pageNumber,pageSize <span class="keyword">int</span>) ([]*Book, error)</span><br><span class="line"> Update(bookId <span class="keyword">int64</span>, bookName <span class="keyword">string</span>) error</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例化,新建</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewBookDao</span><span class="params">()</span> <span class="title">BookDao</span></span> {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Book <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// 具体实现</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里并没有真正的代码实现。代码重点包括3个部分:</p><ul><li><code>interface</code>的定义:核心内容,方便使用者阅读</li><li>实例化:<code>interface</code>怎么创建</li><li>数据结构:<code>interface</code>涉及的结构体</li></ul><p>这一块,非常考验B同学的代码设计能力:怎么样让使用者快速了解你的设计?注释固然是一个不错的方式,但更好的方式是通过函数名、参数名等,详情可参考《 Effective Go》、《代码整洁之道》等资料。</p><h3 id="2-审核接口"><a href="#2-审核接口" class="headerlink" title="2 - 审核接口"></a>2 - 审核接口</h3><p>在B同学完成初步设计后,就需要A同学进行审核。审核主要的目标:<strong>发掘信息差,保证最终功能的正确性</strong>。</p><p>信息差来源于不同模块的上下文差异。比如说,数据库同学更关注底层的数据存储,而API层的同学更关注用户的使用场景。所以,通过评审,A同学可以发现与B同学的信息差,并对接口提出意见、希望B同学加以改进。</p><p>从上面的示例来看,可能存在如下问题,我用注释的方式加以说明(往往结合Code Review):</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> BookDao <span class="keyword">interface</span> {</span><br><span class="line"> <span class="comment">// 需求点1:提供批量创建的能力</span></span><br><span class="line"> Create(book *Book) error</span><br><span class="line"> <span class="comment">// 需求点2:能否支持软删除</span></span><br><span class="line"> Delete(bookId <span class="keyword">int64</span>) error</span><br><span class="line"> <span class="comment">// 需求点3:查询能否支持一些字段的模糊查询,如bookName</span></span><br><span class="line"> <span class="comment">// 需求点4:需要查询满足条件的Book总数,支持分页功能</span></span><br><span class="line"> List(pageNumber,pageSize <span class="keyword">int</span>) ([]*Book, error)</span><br><span class="line"> <span class="comment">// 需求点5:需要修改其余字段,如作者author</span></span><br><span class="line"> Update(bookId <span class="keyword">int64</span>, bookName <span class="keyword">string</span>) error</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 需求点6:字段缺失:如出版商、出版时间</span></span><br><span class="line"><span class="keyword">type</span> Book <span class="keyword">struct</span> {</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接下来,就是A与B的不断沟通、不断修改<code>interface</code>的过程,直到双方基本达成一致。</p><h3 id="3-实现与使用接口"><a href="#3-实现与使用接口" class="headerlink" title="3 - 实现与使用接口"></a>3 - 实现与使用接口</h3><p>随着接口的敲定,接下来的工作就可以兵分两路了:</p><ul><li>B - 提供方去编写这个接口的具体实现,也就是MySQL相关的部分</li><li>A - 使用方调用代码,完成上层业务部分的开发</li></ul><p>可以看到,<code>interface</code>的定义有利于将工作进行拆分,定义关键边界,然后各自完成独立开发。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>从两个角色来看,他们侧重的能力点会有差异:</p><ul><li>提供者:偏向基础能力,主要在于代码的可读性与能力的通用性(不仅仅对接一个使用方)</li><li>使用者:偏向业务能力,需要结合用户使用场景来思考程序设计</li></ul><p>这个情况不仅仅存在于模块间设计,在更大的系统设计时也有非常重要的体现。所以,这是一个很好的提升能力的机会。</p><h2 id="编程思考-提高个人的市场竞争力"><a href="#编程思考-提高个人的市场竞争力" class="headerlink" title="编程思考 - 提高个人的市场竞争力"></a>编程思考 - 提高个人的市场竞争力</h2><p>最近,我经常看到一个问题:<strong>我该学习什么编程能力?</strong> 我会先关心提问者的初衷,常见分为两种:</p><ul><li>提升编程能力</li><li>提高个人的市场竞争力</li></ul><p>相信绝大多数的人都是为了后者。单纯为了提升编程能力的话,自己去啃那些经典教材即可。</p><p>认清并承认自己的目标很重要。关于<strong>程序员的市场竞争力</strong>这个问题,我分享个人的三点看法:</p><ul><li>从ROI的角度分析个人的能力成长方向,把自己当作一个商品(想不清楚的话,多搜搜各大公司的招聘要求)</li><li>多和“高阶人士”交流,扩展视野:可以是职位比你高的领导,也可以是某个方向的资深人员</li><li>为机遇创造条件(能力储备、人脉),但不要一心追求机遇,而是逐步成长</li></ul><h2 id="工作生活-记录生活作息"><a href="#工作生活-记录生活作息" class="headerlink" title="工作生活 - 记录生活作息"></a>工作生活 - 记录生活作息</h2><p>我从本周开始,记录了自己的每日生活作息,分为7类:</p><ul><li>睡眠</li><li>吃饭</li><li>工作</li><li>娱乐</li><li>学习</li><li>运动</li><li>家庭</li></ul><p>记录的动作很简单,拿纸笔就能快速完成,但我在今年才开始真正地做这件事。这背后,我长期存在的一个性格问题:<strong>我清楚自己浪费时间的问题所在,但不敢真正地去面对它,更羞于看到具体的浪费时长。</strong></p><p>我的最终目标也不是为了成为一个时间管理领域的达人,而是 <strong>让自己时间更可控</strong>,减少来自浪费时间的焦虑感。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/weekly.jpg"></p>
<p>作为一名年龄30+程序员,随着2023年的到来,我的工作和生活正在逐步变化。</p>
<p>本系列会以周维度为周期,记录我对如下三块内容的思考,大家挑选各自感兴趣的内容阅读即可:</p>
<ul>
<li><strong>Go技巧</strong> - Go开发者作为重点阅读的群体,不忘初心</li>
<li><strong>编程思考</strong> - 面向所有开发者,从系统设计等高层维度进行分享</li>
<li><strong>工作生活</strong> - 以更贴近生活的视角,分享时间管理、职业发展、焦虑感的一些心得</li>
</ul>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Weekly" scheme="http://example.com/tags/Weekly/"/>
</entry>
<entry>
<title>Go-Buf教程 - 2.【背景篇】Buf生态概览</title>
<link href="http://example.com/2022/12/30/go-buf/go-buf-2/"/>
<id>http://example.com/2022/12/30/go-buf/go-buf-2/</id>
<published>2022-12-30T04:00:00.000Z</published>
<updated>2023-01-09T11:47:52.828Z</updated>
<content type="html"><![CDATA[<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/go-buf.jpg"></p><p>在上一讲,我们明确了<code>Buf</code>套件的定位 - <strong><code>Buf</code>重点是通过对<code>Protobuf Schema</code>的规范化,提供代码生成、依赖管理等一站式的API接口管理方案,来保证开发效率与团队协作的收益最大化。</strong></p><p>那么,<code>Buf</code>生态究竟提供了哪些具体的工具与平台,来支撑其能力呢?通过本篇的分析,你将不仅仅了解<code>Buf</code>项目的设计,更能窥一斑而见全豹,熟悉一个中型开源项目的构成,为后续在自己项目中的落地提供理论支撑。</p><span id="more"></span><h2 id="两大组件-Schema-Registry与CLI"><a href="#两大组件-Schema-Registry与CLI" class="headerlink" title="两大组件 - Schema Registry与CLI"></a>两大组件 - Schema Registry与CLI</h2><p>作为<code>buf</code>主推的两大产品,<code>Schema Registry</code> 和 <code>CLI</code> 的定位非常清晰:</p><ul><li><a href="https://buf.build/product/bsr/">Schema Registry</a> 是一个远端管理<code>buf schema</code>的解决方案,可以类比<code>github</code></li><li><a href="https://docs.buf.build/tour/introduction/">CLI</a> 是一个在本地终端运行的二进制工具,也是与<code>Schema Registry</code>交互的工具</li></ul><p><img src="https://buf.build/static/buf-graph-f73585cb2ce571190da213de0af6e198.svg" alt="Buf"></p><p>对照上面的设计图,这两个产品的功能不难猜测。这里,我换一个角度和大家聊聊这两个产品 - 收费pricing。如果你作为负责人,你会想要怎么收费呢?没错,收费的重点放在<code>Schema Registry</code>上:</p><ul><li><code>CLI</code>作为一个和各终端交互的工具,如果作为收费点,一来很难控制,二来很影响用户的直接体验;</li><li><code>Schema Registry</code>作为一个集中式的、远端的服务,对官方来说容易升级维护,也是协作能力的卖点所在;</li></ul><p>到这里,我们对这两个产品有了一定的理解,而且明确了学习的重点 - <code>CLI</code>的使用。</p><blockquote><p>关于<code>Schema Registry</code>部分,如果公司有一定的基础建设(主要是CICD体系),可以找到替代方案。</p><p>这部分不会作为教程的重点,毕竟影响到了<code>Buf</code>团队的商业模式了。</p></blockquote><h2 id="计费-Pricing"><a href="#计费-Pricing" class="headerlink" title="计费 - Pricing"></a>计费 - Pricing</h2><p>计费模式,是公司对产品经过反复推敲,才选定的最合适、最有价值的核心竞争力。以<a href="https://buf.build/pricing/">buf的计费模式</a>为例,我们来看看3个计费点:</p><ol><li><code>Schema Registry</code>访问方式 - private化</li><li>人工支持 - 24h答疑</li><li><code>Schema Registry</code>进阶能力 - 在平台侧提供各项进阶能力,主要和安全、协作相关</li></ol><p>分析一下这些收费点,会发现开源产品收费策略的一些共性:</p><ul><li>收费点不能限制产品核心流程上的体验</li><li>收费点不要限制产品的推广、传播</li><li>收费点要有明确的目标客户</li></ul><p>收费模式决定了产品的成败,是我们在技术选型时的重要考量点:如果你不认可该产品的收费模式,认为注定失败,那就提前规避风险、换个方案。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本节从<code>Buf</code>的两大组件和收费模式进行了分析,相信大家对<code>Buf</code>的生态有了基本的认识。</p><p>一句话总结:<code>Buf Cli</code>提供了本机上的命令交互,是开发者要重点熟悉的工具;而<code>Schema Registry</code>提供了远端协作的能力,作为产品的核心收费点。</p><blockquote><p>Github: <a href="https://github.com/Junedayday/code_reading">https://github.com/Junedayday/code_reading</a></p><p>Blog: <a href="http://junes.tech/">http://junes.tech/</a></p><p>Bilibili: <a href="https://space.bilibili.com/293775192">https://space.bilibili.com/293775192</a></p><p>公众号: golangcoding</p><p><img src="https://i.loli.net/2021/02/28/RPzy7Hjc9GZ8I3e.jpg" alt="二维码"></p></blockquote>]]></content>
<summary type="html">
<p><img src="https://cloud-fitter-1305666920.cos.ap-beijing.myqcloud.com/go-buf.jpg"></p>
<p>在上一讲,我们明确了<code>Buf</code>套件的定位 - <strong><code>Buf</code>重点是通过对<code>Protobuf Schema</code>的规范化,提供代码生成、依赖管理等一站式的API接口管理方案,来保证开发效率与团队协作的收益最大化。</strong></p>
<p>那么,<code>Buf</code>生态究竟提供了哪些具体的工具与平台,来支撑其能力呢?通过本篇的分析,你将不仅仅了解<code>Buf</code>项目的设计,更能窥一斑而见全豹,熟悉一个中型开源项目的构成,为后续在自己项目中的落地提供理论支撑。</p>
</summary>
<category term="成长分享" scheme="http://example.com/categories/%E6%88%90%E9%95%BF%E5%88%86%E4%BA%AB/"/>
<category term="Go-Buf" scheme="http://example.com/tags/Go-Buf/"/>
</entry>
</feed>