-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
726 lines (701 loc) · 195 KB
/
search.xml
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
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>【折腾笔记】看板娘</title>
<url>/blog/cc002/</url>
<content><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>又来更新了,前两天把网站折腾利索,前端加上了看板娘的js源码,把Live2D模型搬到了服务器上做了看板娘的API,美滋滋,但小水管服务器加载速度太慢了。看板娘可是网站灵魂,要优化!于是开始了新一轮的折腾!(折腾完这一波,我也算是接触过前端相关技术了 23333)</p>
<p>目标:</p>
<ol>
<li>CDN加速(要快, ??</li>
<li>不依赖别人(纸片人老婆怎么能用别人的</li>
<li>模型易增加(??</li>
<li>源码可更改(尽量搞到非压缩非混淆的源码)</li>
</ol>
<h1 id="相关源码"><a href="#相关源码" class="headerlink" title="相关源码"></a>相关源码</h1><p>翻了翻github,找到了这些相关的库</p>
<ol>
<li><a href="https://github.com/stevenjoezhang/live2d-widget">https://github.com/stevenjoezhang/live2d-widget</a></li>
<li><a href="https://github.com/HCLonely/live2d.user.js">https://github.com/HCLonely/live2d.user.js</a></li>
<li><a href="https://github.com/journey-ad/live2d_src">https://github.com/journey-ad/live2d_src</a></li>
<li><a href="https://github.com/EYHN/hexo-helper-live2d">https://github.com/EYHN/hexo-helper-live2d</a></li>
<li><a href="https://github.com/Eikanya/Live2d-model">https://github.com/Eikanya/Live2d-model</a></li>
</ol>
<p>emm,一开始是用的库1的代码,但有个问题,API和CDN方式冲突,API的话模型加载慢,CDN的话不能换衣服2333,所以打算改改看。看了看代码,根源在于live2d.min.js的接口就只接受一个json的path,以这个PATH的相对路径来查找模型资源。所以如果要改的话要么改到live2d.min.js里,要么把原来API返回的JSON都预备好,弃用API,直接给live2d接口CDN的JSON资源,通过换JSON来换衣服。但显然即麻烦又不够优雅,所以决定要改live2d.js。</p>
<p>但库1的live2d.min.js是已经压缩过的,变量名看起来好像也混淆过,难以修改,不是很想用,所以继续找了其他的库。</p>
<p>库2是一个浏览器插件的js源码,并不能直接移植到自己的网站上,但通过里面的一些做法可以得到启发:加载资源时用正则表达式把PATH替换成CDN的path,来即支持live2d-API 也支持CDN加速资源,</p>
<p>库3是基于库4<a href="https://github.com/EYHN/hexo-helper-live2d">hexo-helper-live2d</a>改的,代码能读,功能看起来也OK最终决定基于库3来魔改。</p>
<p>第一次接触js,改了半天吧,还踩了不少坑,{ import/export,ES6的语法,babel的兼容性,Hexo的默认渲染 } 等问题,好在最后搞定了,踩坑的东西就不写了,网上一大把解决方案。</p>
<p>好像不写踩坑的东西,又没啥写的了? 那写写吧</p>
<h1 id="踩坑日志"><a href="#踩坑日志" class="headerlink" title="踩坑日志"></a>踩坑日志</h1><ol>
<li>本地JS代码,和上传的代码不一样,很神奇,<=会变成 <= 1,一开始调试了好久,以为是js或者浏览器或者哪的bug,最后发现是忘了关HEXO的默认渲染,在HEXO的skip_render中添加路径即可。</li>
<li>用本地html文件做测试的时候,加载相对路径的css/js时报错“不支持file://”,emm最后还是hexo s,启server来调试比较方便。</li>
<li><code>type="module"</code>才支持带有import/export的js代码,最后把js变成了单文件没这个问题了。</li>
<li>gulp压缩的时候,不支持ES6语法,看了看gulp的文档,有个Stage-0的plugins,再外加<a href="https://github.com/facebook/regenerator/blob/master/packages/regenerator-runtime/runtime.js">runtime.js</a>,才算通过。</li>
<li>CDN的缓存,更改js不会及时生效,要用版本号/更改文件名等方式来刷新,好像给Github库打tag也行。</li>
</ol>
<h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>日常写C/C++,突然接触js的语法,不检查类型,不检查参数,变量全局,各种不一样,感觉,emm好松!但也挺爽挺好用。</p>
]]></content>
<categories>
<category>折腾笔记</category>
</categories>
<tags>
<tag>不务正业</tag>
<tag>折腾笔记</tag>
<tag>web</tag>
<tag>waifu</tag>
<tag>live2d</tag>
<tag>jsdelivr</tag>
<tag>CDN</tag>
</tags>
</entry>
<entry>
<title>【建站测试】文章格式测试</title>
<url>/blog/cc000/</url>
<content><![CDATA[<h1 id="代码段测试"><a href="#代码段测试" class="headerlink" title="代码段测试"></a>代码段测试</h1><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><vector></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T, <span class="keyword">typename</span>... Tail></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(T head, Tail... tail)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> g(head);</span><br><span class="line"> f(tail...);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">()</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="comment">// clang-format off</span></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">(T x)</span></span>{<span class="built_in">std</span>::<span class="built_in">cout</span> << x << <span class="string">" "</span>; }</span><br><span class="line"><span class="comment">// clang-format on</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="string">"testA:"</span> << <span class="built_in">std</span>::<span class="built_in">endl</span> << <span class="string">"\t"</span>;</span><br><span class="line"> f(<span class="number">1</span>, <span class="number">2.2</span>, <span class="string">"hello"</span>);</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="string">"testB:"</span> << <span class="built_in">std</span>::<span class="built_in">endl</span> << <span class="string">"\t"</span>;</span><br><span class="line"> f(<span class="number">0.2</span>, <span class="number">666</span>, <span class="string">"yuck!"</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="图片测试"><a href="#图片测试" class="headerlink" title="图片测试"></a>图片测试</h1>
<p><img data-src="/images/posts/test.jpg" alt="This is an test image"><br><code>\{\% </code> <code>asset_img /images/posts/test.jpg This is an test image \}\%</code> </p>
<h1 id="表格测试"><a href="#表格测试" class="headerlink" title="表格测试"></a>表格测试</h1><table>
<thead>
<tr>
<th>表头A</th>
<th>表头B</th>
<th>表头C</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
</tbody></table>
<h1 id="标签插件"><a href="#标签插件" class="headerlink" title="标签插件"></a>标签插件</h1><h2 id="标签页"><a href="#标签页" class="headerlink" title="标签页"></a>标签页</h2><div class="tabs" id="fourth-unique-name"><ul class="nav-tabs"><li class="tab active"><a href="#fourth-unique-name-1">Solution 1 点我</a></li><li class="tab"><a href="#fourth-unique-name-2">Solution 2 点我</a></li><li class="tab"><a href="#fourth-unique-name-3">Solution 3 点我</a></li></ul><div class="tab-content"><div class="tab-pane active" id="fourth-unique-name-1"><p><strong>This is Tab 1.</strong></p></div><div class="tab-pane" id="fourth-unique-name-2"><p><strong>This is Tab 2.</strong></p></div><div class="tab-pane" id="fourth-unique-name-3"><p><strong>This is Tab 3.</strong></p></div></div></div>
<h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><hr>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque hendrerit lacus ut purus iaculis feugiat. Sed nec tempor elit, quis aliquam neque. Curabitur sed diam eget dolor fermentum semper at eu lorem.</p>
<hr>
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque hendrerit lacus ut purus iaculis feugiat. Sed nec tempor elit, quis aliquam neque. Curabitur sed diam eget dolor fermentum semper at eu lorem.</p>
</blockquote>
<hr>
<blockquote><p>Do not just seek happiness for yourself. Seek happiness for all. Through kindness. Through mercy.</p>
<footer><strong>David Levithan</strong><cite>Wide Awake</cite></footer></blockquote>
<hr>
<blockquote><p>NEW: DevDocs now comes with syntax highlighting. <a href="http://devdocs.io/">http://devdocs.io</a></p>
<footer><strong>@DevDocs</strong><cite><a href="https://twitter.com/devdocs/status/356095192085962752">twitter.com/devdocs/status/356095192085962752</a></cite></footer></blockquote>
<hr>
<blockquote><p>Every interaction is both precious and an opportunity to delight.</p>
<footer><strong>Seth Godin</strong><cite><a href="http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html">Welcome to Island Marketing</a></cite></footer></blockquote>
<hr>
<h1 id="more"><a href="#more" class="headerlink" title="more"></a>more</h1><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<a id="more"></a>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<h1 id="视频测试"><a href="#视频测试" class="headerlink" title="视频测试"></a>视频测试</h1><p><code>html bilibili</code></p>
<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
<iframe src="//player.bilibili.com/player.html?aid=6692695&bvid=BV1as41147Tg&cid=10896147&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;"> </iframe>
</div>
<p><code>youtube video_id</code></p>
<div class="video-container"><iframe src="https://www.youtube.com/embed/qU6lIArty7E" frameborder="0" loading="lazy" allowfullscreen></iframe></div>
<h1 id="引用文章"><a href="#引用文章" class="headerlink" title="引用文章"></a>引用文章</h1><p>获取永久链接<br>// hello-world</p>
<p>// test</p>
<hr>
<p>超链接</p>
<a href="#">Post not found: hello-world 链接文字</a>
<hr>
<p>markdown语法<br><code>[hello-world](./hello-world.md)</code><br>失败,不过外链可以用这个。</p>
]]></content>
<categories>
<category>建站测试</category>
</categories>
<tags>
<tag>建站测试</tag>
</tags>
</entry>
<entry>
<title>【折腾笔记】个人网站</title>
<url>/blog/cc001/</url>
<content><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>终于暂且算是把博客性质的个人网站折腾利索了,也算是日常不务正业吧,记录下折腾过程吧,以免哪天服务器挂了要重新折腾(希望不需要。</p>
<p>从14年开始搞个人网站,那时候用虚拟主机,便宜耐用,但配置差,网速慢,备份不方便,自由度极低,而且那时候网站程序是用的 WordPress ,Emm 可能是因为当时太菜了,觉得不好用,网站搬家也很费力,备份也不太方便,虽然有一次趁着打折续了五年的,但后来也弃了虚拟主机。</p>
<p>后来开始买国外或香港VPS,免备案,预装Ubuntu,一键xx面板,一键LNMP,面板后台操作一番,WordPress 网站就建起来了,体验还不错。不过感觉xx面板有点不安全,而且依赖性太强,既然是瞎折腾,隔着一层面板折腾来折腾去也的确没什么意思。</p>
<p>再后来docker火热,用 docker 搭了一个 WordPress 的,这个感觉蛮好的,备份一下docker的脚本,备份下数据库文件就好了,搬家也方便。但那时候没管理网站的注册机制,被脚本机器人注册了大量垃圾用户,而且也没怎么维护网站,后来数据库撑炸了,2333,再后来国外服务器总被封,各种原因也没再继续维护个人网站了。</p>
<p>中间还试过GitHub Page,那时候用的叫 JK什么(误)的静态框架,但没什么好看的主题,而且emm反正不是很喜欢,就没继续搞。</p>
<p>这波是2019年了,趁阿里云搞活动买了波基础机型,然后一直没动。直到有一天看到一个人的网站里有看板娘,emmmm,毕竟死宅,i了i了,于是就:”我也要这个!”,开始了这波Hexo博客的搭建。后面是留给自己的折腾笔记。</p>
<hr>
<h1 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>由于平时开发使用Windows+WSL,所以两个平台都试了下,都很香。</p>
<p>不过现在WSL的文件管理还不是很稳定,用Sublime浏览的时候,时而加载不出来,不是很方便,所以最后全都转到Windows下了(WSL近期要更新了 windows可以直接访问wsl文件,那时候应该比较好用,期待一下,就不暂时折腾win10更新的东西了)</p>
<p>依赖:</p>
<ol>
<li>git</li>
<li>node.js</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install hexo-cli -g</span><br><span class="line">npm config <span class="built_in">set</span> registry https://registry.npm.taobao.org</span><br><span class="line">hexo init blog</span><br><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line">hexo new <span class="string">"Hello Hexo"</span></span><br><span class="line">hexo generate</span><br><span class="line">hexo server</span><br></pre></td></tr></table></figure>
<p>大概这样就运行起来了。</p>
<h2 id="配置自动发布"><a href="#配置自动发布" class="headerlink" title="配置自动发布"></a>配置自动发布</h2><p>emm Hexo支持很多种方式自动发布部署,rsync sftp ftpsync git,反正试到最后git最好用。rsync跨系统会有点坑在里面,sftp速度太慢,ftpsync现在的包并不好用,还要打补丁,打完补丁第一天还ok,第二天不好使了 2333。</p>
<p>反正最后折腾到只用git了,git到 GitHub 很简单,到阿里云服务器的话,我是搭了一个 Gitea 服务,然后传到自己的 Gitea 的库里面,然后在 Gitea 的 action 里配置自动发布,大概长这样。。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">git --work-tree=/var/www/html/xxxxx --git-dir=/home/git/gitea-repositories/xxx/xxx.git checkout -f</span><br></pre></td></tr></table></figure>
<p>所以hexo这里的自动发布 就只用git就ok了,github网速慢的问题可以走代理</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git config --global http.proxy http://127.0.0.1:1080</span><br><span class="line">git config --global https.proxy https://127.0.0.1:1080</span><br></pre></td></tr></table></figure>
<p>而且Git可以配置公私钥来免登录,比较方便。</p>
<h2 id="hexo-asset-image"><a href="#hexo-asset-image" class="headerlink" title="hexo-asset-image"></a>hexo-asset-image</h2><p>这个包好像有个问题,一开始图片一直显示不出来,调试发现生成图片链接的相关逻辑不太对,后来把index.js替换掉了就好使了。</p>
<h2 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line">git <span class="built_in">clone</span> https://github.com/next-theme/hexo-theme-next.git themes/next</span><br></pre></td></tr></table></figure>
<p>next主题还是很好用的,也比较喜欢,旧的next库很久不维护了,其中的主要贡献者另起炉灶开了新库(上面的链接)。</p>
<p><strong>关于配置文件</strong>。有两个配置文件,一个是Hexo的,一个是主题的配置文件,有办法统一只使用一个配置文件:在hexo的配置文件最下方增加<code>theme_config:</code>,然后把主题的配置文件内容全部copy到Hexo的下面,并增加2缩进,如:</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">theme_config:</span></span><br><span class="line"> <span class="comment"># ---------------------------------------------------------------</span></span><br><span class="line"> <span class="comment"># Theme Core Configuration Settings</span></span><br><span class="line"> <span class="comment"># See: https://theme-next.org/docs/theme-settings/</span></span><br><span class="line"> <span class="comment"># ---------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># If false, merge configs from `_data/next.yml` into default configuration (rewrite).</span></span><br><span class="line"> <span class="comment"># If true, will fully override default configuration by options from `_data/next.yml` (override). Only for NexT settings.</span></span><br><span class="line"> <span class="comment"># And if true, all config from default NexT `_config.yml` have to be copied into `next.yml`. Use if you know what you are doing.</span></span><br><span class="line"> <span class="comment"># Useful if you want to comment some options from NexT `_config.yml` by `next.yml` without editing default config.</span></span><br><span class="line"> <span class="attr">override:</span> <span class="literal">false</span></span><br><span class="line"> <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<p>这样可以避免更新主题时由于配置不统一导致需要手动处理merge的问题。</p>
<h2 id="看板娘"><a href="#看板娘" class="headerlink" title="看板娘"></a>看板娘</h2><p>参考</p>
<blockquote>
<ol>
<li><a href="https://github.com/stevenjoezhang/live2d-widget">live2d-widget</a></li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><a href="https://www.fghrsh.net/post/123.html">网页添加 Live2D 看板娘</a></li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li><a href="https://imjad.cn/archives/lab/add-dynamic-poster-girl-with-live2d-to-your-blog-01/">给博客添加能动的看板娘</a></li>
</ol>
</blockquote>
<p>增加了部分修改:</p>
<ol>
<li><p>去掉了小飞机。<code>// <span class="fa fa-lg fa-paper-plane"></span></code></p>
</li>
<li><p>去掉了宽度过窄就不显示的逻辑。(让手机端默认显示)</p>
</li>
</ol>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">@media (max-width: 768px) {</span></span><br><span class="line"><span class="comment"> #waifu {</span></span><br><span class="line"><span class="comment"> display: none;</span></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="comment">*/</span></span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// if (screen.width >= 768) {</span></span><br><span class="line">4...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="自建API"><a href="#自建API" class="headerlink" title="自建API"></a>自建API</h2><p>一开始是用的别人的API,但别人API总炸,一炸看板娘就没了。</p>
<p>于是打算自己建API自己用。我在网页方面真心是菜鸟了,工作不搞这方面的东西,知识暂时全靠瞎折腾获取,所以自建API也要写一节!毕竟自己通过查资料试验测试,搞通了之后还是比较有成就感的。</p>
<p>服务端需要有几个东西。</p>
<ol>
<li>php-fpm php-cgi</li>
<li>fastcgi </li>
<li>nginx</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/fghrsh/live2d_api.git</span><br></pre></td></tr></table></figure>
<p>把API直接clone到了Hexo的souce里,这样就能搭车同步了,这也是为什么需要同步的文件又多又大的原因,内置的方法里,只有 Git 能比较轻松的搞定了。</p>
<p>服务端</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt install php-fpm php-cgi</span><br></pre></td></tr></table></figure>
<p>然后在nginx里面配置使用<code>FastCGI server</code> </p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="comment"># pass PHP scripts to FastCGI server</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="attribute">location</span> <span class="regexp">~ \.php$</span> {</span><br><span class="line"> <span class="attribute">include</span> snippets/fastcgi-php.conf;</span><br><span class="line"></span><br><span class="line"> <span class="comment"># With php-fpm (or other unix sockets):</span></span><br><span class="line"> <span class="attribute">fastcgi_pass</span> unix:/var/run/php/php7.2-fpm.sock;</span><br><span class="line"> <span class="comment"># With php-cgi (or other tcp sockets):</span></span><br><span class="line"> <span class="comment"># fastcgi_pass 127.0.0.1:9000;</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="增加模型"><a href="#增加模型" class="headerlink" title="增加模型"></a>增加模型</h2><p>读了读大佬的API的代码,逻辑也不是很复杂,而且提供了增加模型的支持,在<a href="https://mx.paul.ren/page/1/">梦象</a>获取模型后,放到<code>/model/*/</code>里,把<code>model.json</code>重命名为<code>index.json</code>即可,然后修改<code>model_list.json</code>,测试了一下没有对<code>live2d</code>模型动作的依赖。如果有模型大小不合适的,在<code>index.json</code>中修改<code>layout</code>项。</p>
<h1 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h1><p>服务器本地加载Live2d有些慢,毕竟小水管服务器,还是CDN加速比较稳定而且负载小一些,有时间可以搞一下。</p>
<p>Live2d的模型在<a href="https://github.com/Eikanya/Live2d-model">Live2d-model</a>找到了有大佬在收集。</p>
<p>emm,后来也发现了好多大佬的博客做的非常漂亮,暂时不再深入下去,(前端深坑,好可怕……</p>
]]></content>
<categories>
<category>折腾笔记</category>
</categories>
<tags>
<tag>不务正业</tag>
<tag>折腾笔记</tag>
<tag>web</tag>
<tag>waifu</tag>
<tag>live2d</tag>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>【折腾笔记】看板娘浏览器</title>
<url>/blog/cc003/</url>
<content><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><a href="/ny/waifuViewer/" style="text-decoration:none !important;">
<button class="nybtn nybtn_link" onclick="null" ><span onclick="null">waifuViewer</span></button>
</a><br><br><br>
<p>从<a href="https://github.com/Eikanya/Live2d-model">Live2d-model</a>弄了好多模型下来,但是没法看,于是就折腾了个waifuViewer~</p>
<hr>
<p>这几天沉迷个人主页,搞了四五天,emm,虽然自己这几篇文章都打了“不务正业”的标签,但这波也算是开拓视野了。以前从未接触js/css/html开发相关的东西,这算是在自己不熟悉的领域去实现自己的需求,虽然<strong>东西很简单</strong>,但坑处处都是,总的来说挺考验自己的,也从侧面证实了一直以来坚信的一个观点:</p>
<blockquote>
<p>重要的不是知识,而是对知识的应用和理解,以及快速查找知识的能力</p>
<p>重要的不是题解,而是解题的思路和方法,甚至风格、态度、执拗</p>
</blockquote>
<p>当然,<strong>认知</strong>(或者说经验+知识)也很重要。计算机世界的发展除了不断迸发的idea和design,也经历了无数的参考与借鉴,所以很多东西自然而然有<strong>相通</strong>的地方,在遇到新问题的时候,认知面会为解决问题提供支撑。</p>
<p>这段时间虽然”不务正业”,但对博导(前公司老板)的教诲有了新的认识,<strong>WHY-WHAT-HOW</strong>里面,虽然小规模的问题都体现在HOW上,但其实最不重要的就是HOW。计算机中也存在<strong>道法术</strong>。道,是规则、自然法则,上乘;法,是方法、法理,中乘;术,是行为、方式,下乘。以道御术,悟道比修炼法术更高一筹。但要做到这一点,还需要<strong>不断的努力</strong>,道法术三者兼备才能做出最好的策略。</p>
<p>个人主页先放放,要玩玩别的了!</p>
<hr>
<h1 id="踩坑小计"><a href="#踩坑小计" class="headerlink" title="踩坑小计"></a>踩坑小计</h1><table>
<thead>
<tr>
<th>ISSUE</th>
<th>FIXED</th>
</tr>
</thead>
<tbody><tr>
<td>✔ 布局异常</td>
<td>BOX+CSS解决</td>
</tr>
<tr>
<td>✔ 手机显示异常</td>
<td>CSS多设备适配</td>
</tr>
<tr>
<td>✔ 和左下角看板娘冲突</td>
<td>进入页面主动隐藏左下角看板娘</td>
</tr>
<tr>
<td>✔ 跳转会刷新整个页面</td>
<td>使用pjax</td>
</tr>
<tr>
<td>✔ pjax跳转但不加载js</td>
<td>对需要加载的js做pjax-data标记</td>
</tr>
<tr>
<td>✔ 按钮超链接有下划线</td>
<td>css增加nybtn_link 追加处理</td>
</tr>
<tr>
<td>✔ pjax重复加载js报错</td>
<td>优化js</td>
</tr>
</tbody></table>
<p>后面还有todo的话 大概有以下几个方面</p>
<ol>
<li>支持多个live2d同时显示(纸片人老婆+1+1+1 ???</li>
<li>按钮列表换种方式布局,以便随意添加按钮</li>
<li>子页面用scrollbar控制来显示按钮</li>
</ol>
]]></content>
<categories>
<category>折腾笔记</category>
</categories>
<tags>
<tag>不务正业</tag>
<tag>折腾笔记</tag>
<tag>web</tag>
<tag>waifu</tag>
<tag>live2d</tag>
<tag>jsdelivr</tag>
<tag>CDN</tag>
<tag>playground</tag>
</tags>
</entry>
<entry>
<title>【设计模式之美】开篇</title>
<url>/blog/dp001/</url>
<content><![CDATA[<blockquote>
<p>王争《设计模式之美》学习笔记</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>夯实基础,才能走得更远。之前看了很多基础知识的书籍,比如操作系统、组成原理、编译原理等。实际上,这些基础的知识确实很难直接转化成开发“生产力”,但能潜移默化地、间接地提高对技术的理解。</p>
<p>最近觉得很有必要把设计模式重新抓一抓,设计模式和操作系统、组成原理、编译原理等这些基础学科是不一样的。它虽然也算是一门基础知识,但是它和数据结构、算法更像是一道儿的,相比那些更加基础的学科,设计模式能更直接地提高开发能力。如果说数据结构和算法是讨论如何写出高效代码,那设计模式就是讨论如何写出可扩展、可读、可维护的高质量代码,所以,它们跟平时的编码会有直接的关系,也会一定程度上直接影响开发能力。</p>
<h2 id="不要写被人吐槽的烂代码"><a href="#不要写被人吐槽的烂代码" class="headerlink" title="不要写被人吐槽的烂代码"></a>不要写被人吐槽的烂代码</h2><p>经常有人说,“Talk is cheap,show me the code。”</p>
<p>实际上,代码能力是一个程序员最基础的能力,是基本功,是展示一个程序员基础素养的最直接的衡量标准。程序员所写的代码,实际上就是程序员最直接的名片。</p>
<a id="more"></a>
<p>在校实验室多年以及工作四年一直在编码一线,每天写代码、review同事代码、重构遗留系统代码等。这些年包括在校实验室的几年以及工作中,见过很多的烂代码,比如命名不规范、类设计不合理、分层不清晰、没有模块化概念、代码结构混乱、高度耦合等等。这种的代码维护起来非常费劲,添加或者修改一个功能,常常会牵一发而动全身,让人无从下手,恨不得将全部的代码删掉重写!实际上很多时候如果代码规模小,都是把烂代码全部干掉然后撸个新的。</p>
<p>当然,也看到过很多让人眼前一亮的代码,比如前公司的代码。每当看到那种好代码都会立刻对作者产生无比的好感和认可,内心会不由得产生”woc nb”的声音,好代码的后续维护也会很舒心,并且会非常佩服当时设计者的预见性。</p>
<h2 id="提高复杂代码的设计和开发能力"><a href="#提高复杂代码的设计和开发能力" class="headerlink" title="提高复杂代码的设计和开发能力"></a>提高复杂代码的设计和开发能力</h2><p>大部分工程师比较熟悉的都是编程语言、工具、框架这些东西,因为每天的工作就是在框架里根据业务需求,填充代码。实际上,我刚工作的时候,也是做这类事情。相对来说,这样的工作并不需要具备很强的代码设计能力,只要单纯地能理解业务,翻译成代码就可以了。</p>
<p>但是,有一天,leader 让我开发一个跟业务无关的模块,涉及一定程度的架构重构,面对这样稍微复杂的代码设计和开发,就发现有点力不从心,不知从何下手了。因为我知道只是完成功能、代码能用,可能并不复杂,但是要想写出易扩展、易用、易维护的代码,并不容易。好在在leader的引导下完成了设计,并收获了很多新知识,也为后续个人的技术成长奠定了基础。</p>
<p>复杂代码、功能、系统的设计和开发,涉及到很多考虑,如:如何分层、分模块?应该怎么划分类?每个类应该具有哪些属性、方法?怎么设计类之间的交互?该用继承还是组合?该使用接口还是抽象类?怎样做到解耦、高内聚低耦合?该用单例模式还是静态方法?用工厂模式创建对象还是直接 new 出来?如何避免引入设计模式提高扩展性的同时带来的降低可读性问题?等等等等。如果没有对设计模式相关知识(包括设计模式、设计原则、面向对象设计思想等)有太多的了解和积累,这些问题将会让人手足无措。</p>
<h2 id="让读源码、学框架事半功倍"><a href="#让读源码、学框架事半功倍" class="headerlink" title="让读源码、学框架事半功倍"></a>让读源码、学框架事半功倍</h2><p>对于一个有追求的技术人来说,对技术的积累,既要有广度,也要有深度。很多人早早就意识到了这一点,所以在学习框架、中间件的时候,都会抽空去研究研究原理,读一读源码,希望能在深度上有所积累,而不只是略知皮毛,会用而已。</p>
<p>从我的经验和朋友的反馈来看,看源码的时候,经常会遇到看不懂、看不下去的问题。当遇到过这种情况,其实就是积累的基本功还不够,个人能力还不足以看懂这些代码。优秀的开源项目、框架、中间件,代码量、类的个数都会比较多,类结构、类之间的关系极其复杂,常常调用来调用去。所以,为了保证代码的扩展性、灵活性、可维护性等,代码中会使用到很多设计模式、设计原则或者设计思想。如果不懂这些设计模式、原则、思想,在看代码的时候,可能就会琢磨不透作者的设计思路,对于一些很明显的设计思路,可能要花费很多时间才能参悟。相反,如果对设计模式、原则、思想非常了解,一眼就能参透作者的设计思路、设计初衷,很快就可以把脑容量释放出来,重点思考其他问题,代码读起来就会变得轻松了。</p>
<p>实际上,除了看不懂、看不下去的问题,还有一个隐藏的问题,可能自己都发现不了,那就是自己觉得看懂了,实际上,里面的精髓并没有 get 到多少!因为优秀的开源项目、框架、中间件,就像一个集各种高精尖技术在一起的战斗机。如果想剖析它的原理、学习它的技术,而没有积累深厚的基本功,就算把这台战斗机摆在面前,也不能完全参透它的精髓,只是了解个皮毛,看个热闹而已。</p>
<p>因此,学好设计模式相关的知识,不仅能更轻松地读懂开源项目,还能更深入地参透里面的技术精髓,做到事半功倍。</p>
<h2 id="为职场发展做铺垫"><a href="#为职场发展做铺垫" class="headerlink" title="为职场发展做铺垫"></a>为职场发展做铺垫</h2><p>普通的、低级别的开发工程师,只需要把框架、开发工具、编程语言用熟练,再做几个项目练练手,基本上就能应付平时的开发工作了。但是,如果不想一辈子做一个低级的码农,想成长为技术专家、大牛、技术 leader,希望在职场有更高的成就、更好的发展,那就要重视基本功的训练、基础知识的积累。<br>去看大牛写的代码,或者优秀的开源项目,代码写得都非常的优美,质量都很高。如果只是框架用得很溜,架构聊得头头是道,但写出来的代码很烂,让人一眼就能看出很多不合理的、可以改进的地方,那永远都成不了别人心目中的“技术大牛”。</p>
<p>再者,在技术这条职场道路上,当成长到一定阶段之后,势必要承担一些指导培养初级员工、新人,以及 code review 的工作。这个时候,如果自己都对“什么是好的代码?如何写出好的代码?”不了解,那又该如何指导别人,如何让人家信服呢?</p>
<p>还有,如果作为leader负责一个项目整体的开发工作,就需要为开发进度、开发效率和项目质量负责。应用优秀的软件设计可相对有效地尽量避免团队堆砌垃圾代码,以免让整个项目无法维护、添加、修改一个功能都要费老大劲。优秀的设计可为整个团队的开发效率保驾护航。除此之外,代码质量低还会导致 bug 频发,排查困难。整个团队都陷在成天修改无意义的低级 bug、在烂代码中添补丁的事情中。而一个设计良好、易维护的系统,可以解放我们的时间,让我们做些更加有意义、更能提高自己和团队能力的事情。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>设计模式作为一门与编码、开发有着直接关系的基础知识,早点学习外加刻意练习,每写一行代码都是对内功的利用和加深,是可以受益整个职业生涯的事。</p>
]]></content>
<categories>
<category>设计模式之美</category>
</categories>
<tags>
<tag>设计模式之美</tag>
<tag>学习笔记</tag>
</tags>
</entry>
<entry>
<title>【设计模式之美】如何评价代码的好坏</title>
<url>/blog/dp002/</url>
<content><![CDATA[<blockquote>
<p>王争《设计模式之美》学习笔记</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>代码质量的评价有很强的主观性,描述代码质量的词汇也有很多,比如可读性、可维护性、灵活、优雅、简洁。这些词汇是从不同的维度去评价代码质量的。它们之间有互相作用,并不是独立的,比如,代码的可读性好、可扩展性好就意味着代码的可维护性好。代码质量高低是一个综合各种因素得到的结论。并不能通过单一维度去评价一段代码的好坏。</p>
<p>最常用到几个评判代码质量的标准有:可维护性、可读性、可扩展性、灵活性、简洁性、可复用性、可测试性。其中,可维护性、可读性、可扩展性又是提到最多的、最重要的三个评价标准。</p>
<a id="more"></a>
<h2 id="可维护性(maintainability)"><a href="#可维护性(maintainability)" class="headerlink" title="可维护性(maintainability)"></a>可维护性(maintainability)</h2><p>首先来看,什么是代码的“可维护性”?所谓的“维护代码”到底包含哪些具体工作?</p>
<p>落实到编码开发,所谓的“维护”无外乎就是修改 bug、修改老的代码、添加新的代码之类的工作。所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码。所谓“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。</p>
<p>知道,对于一个项目来说,维护代码的时间远远大于编写代码的时间。工程师大部分的时间可能都是花在修修 bug、改改老的功能逻辑、添加一些新的功能逻辑之类的工作上。所以,代码的可维护性就显得格外重要。</p>
<p>维护、易维护、不易维护这三个概念不难理解。不过,对于实际的软件开发来说,更重要的是搞清楚,如何来判断代码可维护性的好坏。</p>
<p>实际上,可维护性也是一个很难量化、偏向对代码整体的评价标准,它有点类似之前提到的“好”“坏”“优雅”之类的笼统评价。代码的可维护性是由很多因素协同作用的结果。代码的可读性好、简洁、可扩展性好,就会使得代码易维护;相反,就会使得代码不易维护。更细化地讲,如果代码分层清晰、模块化好、高内聚低耦合、遵从基于接口而非实现编程的设计原则等等,那就可能意味着代码易维护。除此之外,代码的易维护性还跟项目代码量的多少、业务的复杂程度、利用到的技术的复杂程度、文档是否全面、团队成员的开发水平等诸多因素有关。</p>
<p>所以,从正面去分析一个代码是否易维护稍微有点难度。不过,可以从侧面上给出一个比较主观但又比较准确的感受。如果 bug 容易修复,修改、添加功能能够轻松完成,那就可以主观地认为代码对来说易维护。相反,如果修改一个 bug,修改、添加一个功能,需要花费很长的时间,那就可以主观地认为代码对来说不易维护。<br>你可能会说,这样的评价方式也太主观了吧?没错,是否易维护本来就是针对维护的人来说的。不同水平的人对于同一份代码的维护能力并不是相同的。对于同样一个系统,熟悉它的资深工程师会觉得代码的可维护性还不错,而一些新人因为不熟悉代码,修改 bug、修改添加代码要花费很长的时间,就有可能会觉得代码的可维护性不那么好。这实际上也印证了之前的观点:代码质量的评价有很强的主观性。</p>
<h2 id="可读性(readability)"><a href="#可读性(readability)" class="headerlink" title="可读性(readability)"></a>可读性(readability)</h2><p>软件设计大师 Martin Fowler 曾经说过:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”翻译成中文就是:“任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码。”Google 内部甚至专门有个认证就叫作 Readability。只有拿到这个认证的工程师,才有资格在 code review 的时候,批准别人提交代码。可见代码的可读性有多重要,毕竟,代码被阅读的次数远远超过被编写和执行的次数。</p>
<p>我个人认为,代码的可读性应该是评价代码质量最重要的指标之一。在编写代码的时候,时刻要考虑到代码是否易读、易理解。除此之外,代码的可读性在非常大程度上会影响代码的可维护性。毕竟,不管是修改 bug,还是修改添加功能代码,首先要做的事情就是读懂代码。代码读不大懂,就很有可能因为考虑不周全,而引入新的 bug。</p>
<p>既然可读性如此重要,那又该如何评价一段代码的可读性呢?</p>
<p>需要看代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等。你应该也能感觉到,从正面上,很难给出一个覆盖所有评价指标的列表。这也是无法量化可读性的原因。</p>
<p>实际上,code review 是一个很好的测验代码可读性的手段。如果你的同事可以轻松地读懂你写的代码,那说明你的代码可读性很好;如果同事在读你的代码时,有很多疑问,那就说明你的代码可读性有待提高了。</p>
<h2 id="可扩展性(extensibility)"><a href="#可扩展性(extensibility)" class="headerlink" title="可扩展性(extensibility)"></a>可扩展性(extensibility)</h2><p>可扩展性也是一个评价代码质量非常重要的标准。它表示的代码应对未来需求变化的能力。跟可读性一样,代码是否易扩展也很大程度上决定代码是否易维护。那到底什么是代码的可扩展性呢?</p>
<p>代码的可扩展性表示,在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。</p>
<p>关于代码的扩展性,在后面讲到“对修改关闭,对扩展开放”这条设计原则的时候,我会来详细讲解,今天只需要知道,代码的可扩展性是评价代码质量非常重要的标准就可以了。</p>
<h2 id="灵活性(flexibility)"><a href="#灵活性(flexibility)" class="headerlink" title="灵活性(flexibility)"></a>灵活性(flexibility)</h2><p>灵活性也是描述代码质量的一个常用词汇。比如经常会听到这样的描述:“代码写得很灵活”。那这里的“灵活”该如何理解呢?<br>尽管有很多人用这个词汇来描述代码的质量。但实际上,灵活性是一个挺抽象的评价标准,要给灵活性下个定义也是挺难的。不过,可以想一下,什么情况下才会说代码写得好灵活呢?我这里罗列了几个场景,希望能引发你自己对什么是灵活性的思考。<br>当添加一个新的功能代码的时候,原有的代码已经预留好了扩展点,不需要修改原有的代码,只要在扩展点上添加新的代码即可。这个时候,除了可以说代码易扩展,还可以说代码写得好灵活。<br>当要实现一个功能的时候,发现原有代码中,已经抽象出了很多底层可以复用的模块、类等代码,可以拿来直接使用。这个时候,除了可以说代码易复用之外,还可以说代码写得好灵活。<br>当使用某组接口的时候,如果这组接口可以应对各种使用场景,满足各种不同的需求,除了可以说接口易用之外,还可以说这个接口设计得好灵活或者代码写得好灵活。<br>从刚刚举的场景来看,如果一段代码易扩展、易复用或者易用,都可以称这段代码写得比较灵活。所以,灵活这个词的含义非常宽泛,很多场景下都可以使用。</p>
<h2 id="简洁性(simplicity)"><a href="#简洁性(simplicity)" class="headerlink" title="简洁性(simplicity)"></a>简洁性(simplicity)</h2><p>有一条非常著名的设计原则,你一定听过,那就是 KISS 原则:“Keep It Simple,Stupid”。这个原则说的意思就是,尽量保持代码简单。代码简单、逻辑清晰,也就意味着易读、易维护。在编写代码的时候,往往也会把简单、清晰放到首位。<br>不过,很多编程经验不足的程序员会觉得,简单的代码没有技术含量,喜欢在项目中引入一些复杂的设计模式,觉得这样才能体现自己的技术水平。实际上,思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。这也是一个编程老手跟编程新手的本质区别之一。<br>除此之外,虽然都能认识到,代码要尽量写得简洁,符合 KISS 原则,但怎么样的代码才算足够简洁?不是每个人都能很准确地判断出来这一点。所以,在后面的章节中,当讲到 KISS 原则的时候,我会通过具体的代码实例,详细给你解释,“为什么 KISS 原则看似非常简单、好理解,但实际上用好并不容易”。今天,就暂且不展开详细讲解了。</p>
<h2 id="可复用性(reusability)"><a href="#可复用性(reusability)" class="headerlink" title="可复用性(reusability)"></a>可复用性(reusability)</h2><p>代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码。在后面的很多章节中,都会经常提到“可复用性”这一代码评价标准。<br>比如,当讲到面向对象特性的时候,会讲到继承、多态存在的目的之一,就是为了提高代码的可复用性;当讲到设计原则的时候,会讲到单一职责原则也跟代码的可复用性相关;当讲到重构技巧的时候,会讲到解耦、高内聚、模块化等都能提高代码的可复用性。可见,可复用性也是一个非常重要的代码评价标准,是很多设计原则、思想、模式等所要达到的最终效果。<br>实际上,代码可复用性跟 DRY(Don’t Repeat Yourself)这条设计原则的关系挺紧密的,所以,在后面的章节中,当讲到 DRY 设计原则的时候,我还会讲更多代码复用相关的知识,比如,“有哪些编程方法可以提高代码的复用性”等。</p>
<h2 id="可测试性(testability)"><a href="#可测试性(testability)" class="headerlink" title="可测试性(testability)"></a>可测试性(testability)</h2><p>相对于前面六个评价标准,代码的可测试性是一个相对较少被提及,但又非常重要的代码质量评价标准。代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。</p>
<p><strong>依赖问题</strong><br>如果被测代码依赖了外部系统或者不可控组件,比如,需要依赖数据库、网络通信、文件系统等,那就需要将被测代码与外部系统通过mock的方式解依赖,mock困难则可测性差,考虑使用依赖注入、控制反转的方式进行优化。</p>
<p><strong>未决行为</strong><br>所谓的未决行为逻辑就是,代码的输出是随机或者说不确定的,比如,跟时间、随机数有关的代码,会导致测试困难,一般将未决行为逻辑重新封装。</p>
<p><strong>全局变量</strong><br>全局变量是一种面向过程的编程风格,有种种弊端。实际上,滥用全局变量也让编写单元测试变得困难,测试要不断重置处理全局变量,且不利于并行测试。</p>
<p><strong>静态方法</strong><br>静态方法跟全局变量一样,也是一种面向过程的编程思维。在代码中调用静态方法,有时候会导致代码不易测试。主要原因是静态方法也很难 mock。但是,这个要分情况来看。只有在这个静态方法执行耗时太长、依赖外部资源、逻辑复杂、行为未决等情况下,才需要在单元测试中 mock 这个静态方法。除此之外,如果只是类似 Math.abs() 这样的简单静态方法,并不会影响代码的可测试性,因为本身并不需要 mock。</p>
<p><strong>复杂继承</strong><br>前面提到,相比组合关系,继承关系的代码结构更加耦合、不灵活,更加不易扩展、不易维护。实际上,继承关系也更加难测试。这也印证了代码的可测试性跟代码质量的相关性。如果父类需要 mock 某个依赖对象才能进行单元测试,那所有的子类、子类的子类……在编写单元测试的时候,都要 mock 这个依赖对象。对于层次很深(在继承关系类图中表现为纵向深度)、结构复杂(在继承关系类图中表现为横向广度)的继承关系,越底层的子类要 mock 的对象可能就会越多,这样就会导致,底层子类在写单元测试的时候,要一个一个 mock 很多依赖对象,而且还需要查看父类代码,去了解该如何 mock 这些依赖对象。如果利用组合而非继承来组织类之间的关系,类之间的结构层次比较扁平,在编写单元测试的时候,只需要 mock 类所组合依赖的对象即可。</p>
<p><strong>高耦合代码</strong><br>如果一个类职责很重,需要依赖十几个外部对象才能完成工作,代码高度耦合,那在编写单元测试的时候,可能需要 mock 这十几个依赖的对象。不管是从代码设计的角度来说,还是从编写单元测试的角度来说,这都是不合理的。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>要写出满足这些评价标准的高质量代码,需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。而所有这些编程方法论的最终目的都是为了编写出高质量的代码。</p>
<p>比如,面向对象中的继承、多态能让写出可复用的代码;编码规范能让写出可读性好的代码;设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让写出可复用、灵活、可读性好、易扩展、易维护的代码;设计模式可以让写出易扩展的代码;持续重构可以时刻保持代码的可维护性等等。</p>
]]></content>
<categories>
<category>设计模式之美</category>
</categories>
<tags>
<tag>设计模式之美</tag>
<tag>学习笔记</tag>
</tags>
</entry>
<entry>
<title>【设计模式之美】面向对象、设计原则、设计模式、编程规范、重构</title>
<url>/blog/dp003/</url>
<content><![CDATA[<blockquote>
<p>王争《设计模式之美》学习笔记</p>
</blockquote>
<h2 id="面向对象"><a href="#面向对象" class="headerlink" title="面向对象"></a>面向对象</h2><h3 id="面向对象概述"><a href="#面向对象概述" class="headerlink" title="面向对象概述"></a>面向对象概述</h3><p>现在,主流的编程范式或者编程风格有三种,它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。现在比较流行的编程语言大部分都是面向对象编程语言。大部分项目也都是基于面向对象编程风格开发的。面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式编码实现的基础。</p>
<h3 id="面向对象四大特性概述"><a href="#面向对象四大特性概述" class="headerlink" title="面向对象四大特性概述"></a>面向对象四大特性概述</h3><p>封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方法来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如 Java 中的 private、protected、public 关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。</p>
<p>如果说封装主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类来实现。抽象存在的意义,一方面是修改实现不需要改变定义;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。</p>
<p>继承用来表示类之间的 is-a 关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。</p>
<p>多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。</p>
<a id="more"></a>
<h3 id="封装(Encapsulation)"><a href="#封装(Encapsulation)" class="headerlink" title="封装(Encapsulation)"></a>封装(Encapsulation)</h3><p>封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。</p>
<p>如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一方面来说,过度灵活也意味着不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。比如某个同事在不了解业务逻辑的情况下,在某段代码中“偷偷地”重设了对象的某个属性,会导致数据不一致等各种问题。</p>
<p>除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。而这对于调用者来说也是一种负担。相反,如果我们将属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。这就好比,如果一个冰箱有很多按钮,你就要研究很长时间,还不一定能操作正确。相反,如果只有几个必要的按钮,比如开、停、调节温度,你一眼就能知道该如何来操作,而且操作出错的概率也会降低很多。</p>
<h3 id="抽象(Abstraction)"><a href="#抽象(Abstraction)" class="headerlink" title="抽象(Abstraction)"></a>抽象(Abstraction)</h3><p>封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。</p>
<p>在面向对象编程中,我们常借助编程语言提供的接口类或者抽象类来实现抽象这一特性。</p>
<p>除此之外,抽象有时候会被排除在面向对象的四大特性之外,抽象这个概念是一个非常通用的设计思想,并不单单用在面向对象编程中,也可以用来指导架构设计等。而且这个特性也并不需要编程语言提供特殊的语法机制来支持,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性、所以,它没有很强的“特异性”,有时候并不被看作面向对象编程的特性之一。</p>
<p>实际上,如果上升一个思考层面的话,抽象及其前面讲到的封装都是人类处理复杂性的有效手段。在面对复杂系统的时候,人脑能承受的信息复杂程度是有限的,所以我们必须忽略掉一些非关键性的实现细节。而抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。</p>
<p>除此之外,抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。</p>
<p>换一个角度来考虑,我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。</p>
<h3 id="继承(Inheritance)"><a href="#继承(Inheritance)" class="headerlink" title="继承(Inheritance)"></a>继承(Inheritance)</h3><p>继承是用来表示类之间的 is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。</p>
<p>为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如 Java 使用 extends 关键字来实现继承,C++ 使用冒号(class B : public A),Python 使用 paraentheses(),Ruby 使用 <。不过,有些编程语言只支持单继承,不支持多重继承,比如 Java、PHP、C#、Ruby 等,而有些编程语言既支持单重继承,也支持多重继承,比如 C++、Python、Perl 等。</p>
<p>继承最大的一个好处就是代码复用。假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。不过,这一点也并不是继承所独有的,我们也可以通过其他方式来解决这个代码复用的问题,比如利用组合关系而不是继承关系。</p>
<p>如果我们再上升一个思维层面,去思考继承这一特性,可以这么理解:我们代码中有一个猫类,有一个哺乳动物类。猫属于哺乳动物,从人类认知的角度上来说,是一种 is-a 关系。我们通过继承来关联两个类,反应真实世界中的这种关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。</p>
<p>继承的概念很好理解,也很容易使用。不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还需要按照继承关系一层一层地往上查看“父类、父类的父类……”的代码。还有,子类和父类高度耦合,修改父类的代码,会直接影响到子类。</p>
<p>所以,继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式。我们应该尽量少用,甚至不用。</p>
<h3 id="多态(Polymorphism)"><a href="#多态(Polymorphism)" class="headerlink" title="多态(Polymorphism)"></a>多态(Polymorphism)</h3><p>多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现。</p>
<p>对于多态特性的实现方式,除了利用“继承加方法重写”这种实现方式之外,我们还有其他两种比较常见的的实现方式,一个是利用接口类语法,另一个是利用 duck-typing 语法。</p>
<p>多态特性能提高代码的可扩展性和复用性。除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。</p>
<h3 id="面向对象-VS-面向过程"><a href="#面向对象-VS-面向过程" class="headerlink" title="面向对象 VS 面向过程"></a>面向对象 VS 面向过程</h3><p>面向对象编程相比面向过程编程的优势主要有三个。</p>
<ul>
<li><p>对于大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。</p>
</li>
<li><p>面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。</p>
</li>
<li><p>从编程语言跟机器打交道方式的演进规律中,可以总结出:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。</p>
</li>
</ul>
<p>面向对象编程一般使用面向对象编程语言来进行,但是,不用面向对象编程语言,照样可以进行面向对象编程。反过来讲,即便使用面向对象编程语言,写出来的代码也不一定是面向对象编程风格的,也有可能是面向过程编程风格的。</p>
<p>面向对象和面向过程两种编程风格并不是非黑即白、完全对立的。在用面向对象编程语言开发的软件中,面向过程风格的代码并不少见,甚至在一些标准的开发库(比如 JDK、Apache Commons、Google Guava)中,也有很多面向过程风格的代码。</p>
<p>不管使用面向过程还是面向对象哪种风格来写代码,最终的目的还是写出易维护、易读、易复用、易扩展的高质量代码。只要能避免面向过程编程风格的一些弊端,控制好它的副作用,在掌控范围内为所用,就大可不用避讳在面向对象编程中写面向过程风格的代码。</p>
<h3 id="面向对象分析、设计与编程"><a href="#面向对象分析、设计与编程" class="headerlink" title="面向对象分析、设计与编程"></a>面向对象分析、设计与编程</h3><p>面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程(OOP),是面向对象开发的三个主要环节。简单点讲,面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做,面向对象编程就是将分析和设计的的结果翻译成代码的过程。</p>
<p>需求分析的过程实际上是一个不断迭代优化的过程。不要试图一下就给出一个完美的解决方案,而是先给出一个粗糙的、基础的方案,有一个迭代的基础,然后再慢慢优化。这样一个思考过程能让摆脱无从下手的窘境。</p>
<p>面向对象设计和实现要做的事情就是把合适的代码放到合适的类中。至于到底选择哪种划分方法,判定的标准是让代码尽量地满足“松耦合、高内聚”、单一职责、对扩展开放对修改关闭等之前讲到的各种设计原则和思想,尽量地做到代码可复用、易读、易扩展、易维护。</p>
<p>面向对象分析的产出是详细的需求描述。面向对象设计的产出是类。在面向对象设计这一环节中,将需求描述转化为具体的类的设计。这个环节的工作可以拆分为下面四个部分。</p>
<ul>
<li><p>划分职责进而识别出有哪些类<br>根据需求描述,把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。</p>
</li>
<li><p>定义类及其属性和方法<br>识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。</p>
</li>
<li><p>定义类与类之间的交互关系<br>UML 统一建模语言中定义了六种类之间的关系。它们分别是:泛化、实现、关联、聚合、组合、依赖。从更加贴近编程的角度,对类与类之间的关系做了调整,保留了四个关系:泛化、实现、组合、依赖。</p>
</li>
<li><p>将类组装起来并提供执行入口<br>要将所有的类组装在一起,提供一个执行入口。这个入口可能是一个 main() 函数,也可能是一组给外部用的 API 接口。通过这个入口,能触发整个代码跑起来。</p>
</li>
</ul>
<h3 id="接口-VS-抽象类"><a href="#接口-VS-抽象类" class="headerlink" title="接口 VS 抽象类"></a>接口 VS 抽象类</h3><p>抽象类不允许被实例化,只能被继承。它可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫作抽象方法。子类继承抽象类,必须实现抽象类中的所有抽象方法。接口不能包含属性(Java 可以定义静态常量),只能声明方法,方法不能包含代码实现(Java8 以后可以有默认实现)。类实现接口的时候,必须实现接口中声明的所有方法。</p>
<p>抽象类是对成员变量和方法的抽象,是一种 is-a 关系,是为了解决代码复用问题。接口仅仅是对方法的抽象,是一种 has-a 关系,表示具有某一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。</p>
<p>什么时候该用抽象类?什么时候该用接口?实际上,判断的标准很简单。如果要表示一种 is-a 的关系,并且是为了解决代码复用问题,就用抽象类;如果要表示一种 has-a 关系,并且是为了解决抽象而非代码复用问题,那就用接口。</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><ul>
<li><p>为什么不推荐使用继承?<br>继承是面向对象的四大特性之一,用来表示类之间的 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,应该尽量少用,甚至不用继承。</p>
</li>
<li><p>组合相比继承有哪些优势?<br>继承主要有三个作用:表示 is-a 关系、支持多态特性、代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。</p>
</li>
<li><p>如何判断该用组合还是继承?<br>尽管鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,还是要根据具体的情况,来选择该用继承还是组合。如果类之间的继承结构稳定,层次比较浅,关系不复杂,就可以大胆地使用继承。反之,就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。</p>
</li>
</ul>
<h2 id="设计原则"><a href="#设计原则" class="headerlink" title="设计原则"></a>设计原则</h2><p>设计原则是指导代码设计的一些经验总结。设计原则这块儿的知识有一个非常大的特点,那就是这些原则听起来都比较抽象,定义描述都比较模糊,不同的人会有不同的解读。所以,如果单纯地去记忆定义,对于编程、设计能力的提高,意义并不大。对于每一种设计原则,需要掌握它的设计初衷,能解决哪些编程问题,有哪些应用场景。只有这样,才能在项目中灵活恰当地应用这些原则。对于这一部分内容,需要透彻理解并且掌握,如何应用下面这样几个常用的设计原则。</p>
<ul>
<li>SOLID 原则 -SRP 单一职责原则</li>
<li>SOLID 原则 -OCP 开闭原则</li>
<li>SOLID 原则 -LSP 里式替换原则</li>
<li>SOLID 原则 -ISP 接口隔离原则</li>
<li>SOLID 原则 -DIP 依赖倒置原则</li>
<li>DRY 原则、KISS 原则、YAGNI 原则、LOD 法则</li>
</ul>
<h3 id="SRP-单一职责原则"><a href="#SRP-单一职责原则" class="headerlink" title="SRP 单一职责原则"></a>SRP 单一职责原则</h3><p>一个类只负责完成一个职责或者功能。单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、松耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。</p>
<p>不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:</p>
<ul>
<li>类中的代码行数、函数或者属性过多;</li>
<li>类依赖的其他类过多或者依赖类的其他类过多;</li>
<li>私有方法过多;</li>
<li>比较难给类起一个合适的名字;</li>
<li>类中大量的方法都是集中操作类中的某几个属性。</li>
</ul>
<h3 id="OCP-开闭原则"><a href="#OCP-开闭原则" class="headerlink" title="OCP 开闭原则"></a>OCP 开闭原则</h3><p><strong>如何理解“对扩展开放、修改关闭”?</strong></p>
<p>添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。关于定义,有两点要注意。第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。</p>
<p><strong>如何做到“对扩展开放、修改关闭”?</strong></p>
<p>要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。</p>
<p>很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的的。特别是 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。</p>
<h3 id="LSP-里式替换原则"><a href="#LSP-里式替换原则" class="headerlink" title="LSP 里式替换原则"></a>LSP 里式替换原则</h3><p>子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。</p>
<p>里式替换原则是用来指导继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数的原有“约定”。这里的“约定”包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。</p>
<p>理解这个原则,还要弄明白,里式替换原则跟多态的区别。虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。</p>
<h3 id="ISP-接口隔离原则"><a href="#ISP-接口隔离原则" class="headerlink" title="ISP 接口隔离原则"></a>ISP 接口隔离原则</h3><p>接口隔离原则的描述是:客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。</p>
<p>如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。</p>
<p>如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。</p>
<p>如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。</p>
<p>单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考的角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。</p>
<h3 id="DIP-依赖倒置原则"><a href="#DIP-依赖倒置原则" class="headerlink" title="DIP 依赖倒置原则"></a>DIP 依赖倒置原则</h3><p><strong>控制反转</strong>:实际上,控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。</p>
<p><strong>依赖注入</strong>:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或“注入”)给类来使用。</p>
<p><strong>依赖注入框架</strong>:通过依赖注入框架提供的扩展点,简单配置一下所有需要的类及其类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。</p>
<p><strong>依赖反转原则</strong>:依赖反转原则也叫作依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不需要依赖具体实现细节,具体实现细节依赖抽象。</p>
<h3 id="KISS、YAGNI-原则"><a href="#KISS、YAGNI-原则" class="headerlink" title="KISS、YAGNI 原则"></a>KISS、YAGNI 原则</h3><p>KISS 原则的中文描述是:尽量保持简单。KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的“简单“”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,也并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。</p>
<p>对于如何写出满足 KISS 原则的代码,总结了下面几条指导原则:</p>
<ul>
<li>不要使用同事可能不懂的技术来实现代码;</li>
<li>不要重复造轮子,善于使用已经有的工具类库;</li>
<li>不要过度优化。</li>
</ul>
<p>YAGNI 原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不会需要它。这条原则也算是万金油了。当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。</p>
<p>YAGNI 原则跟 KISS 原则并非一回事儿。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。</p>
<h3 id="DRY-原则"><a href="#DRY-原则" class="headerlink" title="DRY 原则"></a>DRY 原则</h3><p>DRY 原则中文描述是:不要重复自己,将它应用在编程中,可以理解为:不要写重复的代码。</p>
<p>之前提到了三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。而代码执行重复也算是违反 DRY 原则。</p>
<p>除此之外,还提到了提高代码复用性的一些手段,包括:减少代码耦合、满足单一职责原则、模块化、业务与非业务逻辑分离、通用代码下沉、继承、多态、抽象、封装、应用模板等设计模式。复用意识也非常重要。在设计每个模块、类、函数的时候,要像设计一个外部 API 一样去思考它的复用性。</p>
<p>在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那就不需要考虑代码的复用性。在之后开发新的功能的时候,发现可以复用之前写的这段代码,那就重构这段代码,让其变得更加可复用。</p>
<p>相比于代码的可复用性,DRY 原则适用性更强些。可以不写可复用的代码,但一定不能写重复的代码。</p>
<h3 id="LOD-原则"><a href="#LOD-原则" class="headerlink" title="LOD 原则"></a>LOD 原则</h3><p><strong>如何理解“高内聚、松耦合”?</strong></p>
<p>“高内聚、松耦合”是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓“松耦合”指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。</p>
<p><strong>如何理解“迪米特法则”?</strong></p>
<p>迪米特法则的描述为:不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。</p>
<h2 id="编程规范"><a href="#编程规范" class="headerlink" title="编程规范"></a>编程规范</h2><p>编程规范主要解决的是代码的可读性问题。编程规范相对于设计原则、设计模式,更加具体、更加偏重代码细节。即便可能对设计原则不熟悉、对设计模式不了解,但最起码要掌握基本的编程规范,比如,如何给变量、类、函数命名,如何写代码注释,函数不宜过长、参数不能过多等等。</p>
<p>对于编程规范,考虑到很多书籍已经讲得很好了(比如《重构》《代码大全》《代码整洁之道》等)。而且,每条编程规范都非常简单、非常明确,比较偏向于记忆,只要照着来做可以。它不像设计原则,需要融入很多个人的理解和思考。</p>
<h2 id="重构与解耦"><a href="#重构与解耦" class="headerlink" title="重构与解耦"></a>重构与解耦</h2><p>在软件开发中,只要软件在不停地迭代,就没有一劳永逸的设计。随着需求的变化,代码的不停堆砌,原有的设计必定会存在这样那样的问题。针对这些问题,就需要进行代码重构。重构是软件开发中非常重要的一个环节。持续重构是保持代码质量不下降的有效手段,能有效避免代码腐化到无可救药的地步。</p>
<p>而重构的工具就是前面罗列的那些面向对象设计思想、设计原则、设计模式、编程规范。实际上,设计思想、设计原则、设计模式一个最重要的应用场景就是在重构的时候。前面讲过,虽然使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。在开发初期,除非特别必须,一定不要过度设计,应用复杂的设计模式。而是当代码出现问题的时候,再针对问题,应用原则和模式进行重构,这样就能有效避免前期的过度设计。</p>
<h3 id="重构"><a href="#重构" class="headerlink" title="重构"></a>重构</h3><p><strong>重构的目的:为什么重构(why)?</strong><br>对于项目来言,重构可以保持代码质量持续处于一个可控状态,不至于腐化到无可救药的地步。对于个人而言,重构非常锻炼一个人的代码能力,并且是一件非常有成就感的事情。它是学习的经典设计思想、原则、模式、编程规范等理论知识的练兵场。</p>
<p><strong>重构的对象:重构什么(what)?</strong><br>按照重构的规模,可以将重构大致分为大规模高层次的重构和小规模低层次的重构。大规模高层次重构包括对代码分层、模块化、解耦、梳理类之间的交互关系、抽象复用组件等等。这部分工作利用的更多的是比较抽象、比较顶层的设计思想、原则、模式。小规模低层次的重构包括规范命名、注释、修正函数参数过多、消除超大类、提取重复代码等编程细节问题,主要是针对类、函数级别的重构。小规模低层次的重构更多的是利用编码规范这一理论知识。</p>
<p><strong>重构的时机:什么时候重构(when)?</strong><br>一定要建立持续重构意识,把重构作为开发必不可少的部分融入到开发中,而不是等到代码出现很大问题的时候,再大刀阔斧地重构。</p>
<p><strong>重构的方法:如何重构(how)?</strong><br>大规模高层次的重构难度比较大,需要有组织、有计划地进行,分阶段地小步快跑,时刻保持代码处于一个可运行的状态。而小规模低层次的重构,因为影响范围小,改动耗时短,所以,只要愿意并且有时间,随时随地都可以去做。</p>
<h3 id="解耦"><a href="#解耦" class="headerlink" title="解耦"></a>解耦</h3><p><strong>“解耦”为何如此重要?</strong><br>过于复杂的代码往往在可读性、可维护性上都不友好。解耦,保证代码松耦合、高内聚,是控制代码复杂度的有效手段。如果代码高内聚、松耦合,也就是意味着,代码结构清晰、分层、模块化合理、依赖关系简单、模块或类之间的耦合小,那代码整体的质量就不会差。</p>
<p><strong>代码是否需要“解耦”?</strong><br>间接的衡量标准有很多,比如:改动一个模块或类的代码受影响的模块或类是否有很多、改动一个模块或者类的代码依赖的模块或者类是否需要改动、代码的可测试性是否好等等。直接的衡量标准是把模块与模块之间及其类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。</p>
<p><strong>如何给代码“解耦”?</strong><br>给代码解耦的方法有:封装与抽象、中间层、模块化,以及一些其他的设计思想与原则,比如:单一职责原则、基于接口而非实现编程、依赖注入、多用组合少用继承、迪米特法则。当然,还有一些设计模式,比如观察者模式。</p>
<h2 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h2><p>设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。这一块的学习难点是了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。</p>
<p>经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。</p>
<p>它们又可以分为三大类:创建型、结构型、行为型。对于这 23 种设计模式的学习,要有侧重点,因为有些模式是比较常用的,有些模式是很少被用到的。对于常用的设计模式,要花多点时间理解掌握。对于不常用的设计模式,只需要稍微了解即可。</p>
<p>按照类型和是否常用,对这些设计模式,进行了简单的分类,具体如下所示。<br><strong>1. 创建型</strong><br>常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。<br>不常用的有:原型模式。<br><strong>2. 结构型</strong><br>常用的有:代理模式、桥接模式、装饰者模式、适配器模式。<br>不常用的有:门面模式、组合模式、享元模式。<br><strong>3. 行为型</strong><br>常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。<br>不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。</p>
<h2 id="五者之间的联系"><a href="#五者之间的联系" class="headerlink" title="五者之间的联系"></a>五者之间的联系</h2><p>关于面向对象、设计原则、设计模式、编程规范和代码重构,这五者的关系前面稍微提到了一些,这里再总结梳理一下。</p>
<ul>
<li>面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。</li>
<li>设计原则是指导代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。</li>
<li>设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。</li>
<li>编程规范主要解决的是代码的可读性问题。编程规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。</li>
<li>重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编程规范这些理论。</li>
</ul>
<p>实际上,面向对象、设计原则、设计模式、编程规范、代码重构,这五者都是保持或者提高代码质量的方法论,本质上都是服务于编写高质量代码这一件事的。当追本逐源,看清这个本质之后,很多事情怎么做就清楚了,很多选择怎么选也清楚了。比如,在某个场景下,该不该用这个设计模式,那就看能不能提高代码的可扩展性;要不要重构,那就看重代码是否存在可读、可维护问题等。</p>
]]></content>
<categories>
<category>设计模式之美</category>
</categories>
<tags>
<tag>设计模式之美</tag>
<tag>学习笔记</tag>
</tags>
</entry>
<entry>
<title>【C++】宏编程艺术初探</title>
<url>/blog/cc004/</url>
<content><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>“对于任何事物,代码中只应该出现一次,而且是唯一的一次。 如果你在repeat,要么是在偷懒,要么是在犯错”——鲁迅</p>
<p>在写业务代码的时候,即使基础设施已经足够优秀,但仍然不可避免要写很多重复代码,于是研究了一下C++中消除重复的某些手段:如模板元编程、宏编程(预处理元编程)。</p>
<p>其中模板元编程这个屠龙之技有部分局限性:</p>
<ol>
<li>不能通过 模板展开 生成新的 标识符(例如 生成新的 函数名、类名、名字空间名 等)</li>
<li>使用者 只能使用 预先定义的标识符(否则不识别标识符编译不通过 unknow symbol xxx)</li>
<li>不能通过 模板参数 获取名称的字面量,使用者只能通过传递字符串参数绕开限制 (宏编程中可以使用#获取字面量)</li>
<li>受C++标准及编译器版本限制</li>
</ol>
<p>在探究宏编程邪教时,发现宏编程十分适用于我所遇到的痛点问题,而且这次引发需求的场景更需要批量生成标识符,于是选择了使用宏编程技术解决该痛点。</p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在启动这次研究之前,对于宏编程的认识还停留在”简单的文本替换而已”,对于预处理只经常接触以下几种基本简单应用:</p>
<ol>
<li>头文件保护</li>
<li>适配平台</li>
<li>选择性编译</li>
<li>小公共代码块替换</li>
<li>单元测试hack-mock</li>
</ol>
<p>平时开发最经常接触的就是小代码块了,常见的比如</p>
<table>
<thead>
<tr>
<th>例子</th>
<th>代码</th>
</tr>
</thead>
<tbody><tr>
<td>使用宏拼接命名</td>
<td><code>#define CONCAT_NAME(a, b) a##b</code></td>
</tr>
<tr>
<td>使用宏将名称转为字符串</td>
<td><code>#define CONCAT(a, b) a##b</code></td>
</tr>
<tr>
<td>重复代码抽离</td>
<td><code>#define DO_SOME_THING() do{balabala;}while(0)</code></td>
</tr>
<tr>
<td>C-STYLE日志</td>
<td><code>#define log_debug(...) log("debug" ,__function__ , __LINE__, __VA_ARGS__)</code></td>
</tr>
</tbody></table>
<p>这次研究引入了以下几个核心知识点</p>
<ol>
<li>预处理三阶段(预扫描、扫描、替换)</li>
<li>参数何时展开 展开何时终止</li>
<li>宏的延迟展开与提前展开手段</li>
<li>宏数据结构 (元组 序列 列表)</li>
<li>Clang、MSVC、GCC各编译器宏处理的差异(跨平台坑)</li>
</ol>
<p>由这些知识点、可以获取以下宏编程基本手法</p>
<ol>
<li>惰性求值、延迟调用、延迟展开</li>
<li>逻辑运算、布尔转换、条件选择</li>
<li>长度判空、长度计算、下标访问</li>
<li>借助以上N点原理,实现偷懒利器:宏遍历</li>
</ol>
<a id="more"></a>
<h1 id="需求场景"><a href="#需求场景" class="headerlink" title="需求场景"></a>需求场景</h1><p>当前每次版本迭代需要对接后台实现新feature,于是要写大量类似代码,这里虚构一些代码说明几个明显的痛点。</p>
<h2 id="痛点代码示例"><a href="#痛点代码示例" class="headerlink" title="痛点代码示例"></a>痛点代码示例</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NemoAppDemo</span> :</span> <span class="keyword">public</span> NemoApiRoute</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 声明api 每个api单独方法处理</span></span><br><span class="line"> <span class="comment">// API多的时候 声明搞的很多 类看着很庞大 无形增加维护人员的心理压力</span></span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api0</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api1</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api2</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api3</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api4</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api5</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params, <span class="keyword">bool</span> bCallback)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api6</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params, <span class="keyword">bool</span> bCallback)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api7</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api8</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params, <span class="keyword">bool</span> bCallback, <span class="keyword">bool</span> bFromNotify, Poco::VariantMap param)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api9</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="comment">// 注册api</span></span><br><span class="line"> RegisterRouterFunctions()</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// API绑定方法 (bind到眼花,增删API时极易出错)</span></span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API0, <span class="built_in">std</span>::bind(&NemoAppDemo::api0, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API1, <span class="built_in">std</span>::bind(&NemoAppDemo::api1, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API2, <span class="built_in">std</span>::bind(&NemoAppDemo::api2, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API3, <span class="built_in">std</span>::bind(&NemoAppDemo::api3, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> <span class="comment">//RegisterRouterFunction(NEMO_API_DEMO_API4, std::bind(&NemoAppDemo::api4, this, _1, _2));</span></span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API5, <span class="built_in">std</span>::bind(&NemoAppDemo::api5, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API6, <span class="built_in">std</span>::bind(&NemoAppDemo::api6, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> <span class="comment">// 有的类型不一致 更改时更需要谨慎</span></span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API7, <span class="built_in">std</span>::bind(&NemoAppDemo::api7, <span class="keyword">this</span>, _1, _2, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">false</span>));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API8, <span class="built_in">std</span>::bind(&NemoAppDemo::api8, <span class="keyword">this</span>, _1, _2, <span class="literal">true</span>));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API9, <span class="built_in">std</span>::bind(&NemoAppDemo::api9, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API10, <span class="built_in">std</span>::bind(&NemoAppDemo::api10, <span class="keyword">this</span>, _1, _2, <span class="literal">true</span>, <span class="literal">false</span>, Poco::VariantMap()));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API11, <span class="built_in">std</span>::bind(&NemoAppDemo::api11, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(NEMO_API_DEMO_API12, <span class="built_in">std</span>::bind(&NemoAppDemo::api12, <span class="keyword">this</span>, _1, _2));</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><br><span class="line"> <span class="comment">// CACHE 结构的维护</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">Cache</span></span></span><br><span class="line"><span class="class"> {</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> bala;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> bala;</span><br><span class="line"> <span class="keyword">uint64_t</span> bala;</span><br><span class="line"> <span class="keyword">int64_t</span> bala;</span><br><span class="line"> <span class="keyword">int32_t</span> balbalabla1;</span><br><span class="line"> <span class="keyword">int32_t</span> adlfajskjal3;</span><br><span class="line"> <span class="keyword">int32_t</span> lakjdfalksfjdalw3;</span><br><span class="line"> <span class="keyword">int32_t</span> a2fli3jlakf;</span><br><span class="line"> <span class="keyword">int32_t</span> aldfkja32ka;</span><br><span class="line"> <span class="keyword">int32_t</span> lksdjfal3wia;</span><br><span class="line"> <span class="keyword">int32_t</span> lajflwkj3;</span><br><span class="line"> <span class="keyword">int32_t</span> alalskdfksj3ijf;</span><br><span class="line"> <span class="keyword">int32_t</span> ajlskfdjaliwf;</span><br><span class="line"> <span class="keyword">int32_t</span> lawfj3wkjaja;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">ToMap</span><span class="params">(Poco::VariantMap& mapParams)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="comment">// 本地cache构造回包</span></span><br><span class="line"> <span class="comment">// 稍有不慎 经常缺参数 参数错误 类型转换crash 各种奇怪问题</span></span><br><span class="line"> <span class="comment">// 开发过程难免需求变更、后台字段变更,变更后更易出错</span></span><br><span class="line"> mapParams[<span class="string">"lawfj3wkjaja"</span>] = bala.c_str()?bala.c_str():<span class="string">""</span>; </span><br><span class="line"> mapParams[<span class="string">"balbalabla1"</span>] = balbalabla1;</span><br><span class="line"> mapParams[<span class="string">"adlfajskjal3"</span>] = adlfajskjal3;</span><br><span class="line"> mapParams[<span class="string">"lakjdfalksfjdalw3"</span>] = lakjdfalksfjdalw3;</span><br><span class="line"> mapParams[<span class="string">"a2fli3jlakf"</span>] = a2fli3jlakf;</span><br><span class="line"> mapParams[<span class="string">"aldfkja32ka"</span>] = aldfkja32ka;</span><br><span class="line"> mapParams[<span class="string">"lksdjfal3wia"</span>] = lksdjfal3wia;</span><br><span class="line"> mapParams[<span class="string">"lajflwkj3"</span>] = lajflwkj3;</span><br><span class="line"> mapParams[<span class="string">"alalskdfksj3ijf"</span>] = alalskdfksj3ijf;</span><br><span class="line"> mapParams[<span class="string">"ajlskfdjaliwf"</span>] = ajlskfdjaliwf;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">FromJson</span><span class="params">(<span class="keyword">const</span> NemoJsonParser& json)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="comment">// 解析json</span></span><br><span class="line"> <span class="comment">// 类型错误轻则业务逻辑出错 重则直接crash查到头秃</span></span><br><span class="line"> <span class="comment">// 1. 稍有不慎 搞错字段 逻辑错误</span></span><br><span class="line"> <span class="comment">// 2. 后台如果改字段 需要变更多处代码 稍有不慎 逻辑错误</span></span><br><span class="line"> <span class="comment">// 3. 类型转换失效 稍有不慎 运行时崩溃</span></span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"data"</span>)) {</span><br><span class="line"> <span class="keyword">auto</span> data = json[<span class="string">"data"</span>];</span><br><span class="line"> <span class="keyword">if</span> (data.HasMember(<span class="string">"info"</span>)) {</span><br><span class="line"> <span class="keyword">auto</span> Info = data[<span class="string">"info"</span>];</span><br><span class="line"> Poco::VariantMap rspMap;</span><br><span class="line"> <span class="keyword">int</span> state = Info[<span class="string">"_state"</span>];</span><br><span class="line"> uint64 lkajfds = Info[<span class="string">"lkajfds"</span>];</span><br><span class="line"> uint64 durationss = Info[<span class="string">"durationss"</span>];</span><br><span class="line"> <span class="keyword">int</span> usercount = Info[<span class="string">"usercount"</span>];</span><br><span class="line"> uint64 alwkjef = Info[<span class="string">"alwkjef"</span>];</span><br><span class="line"> uint64 lkdfsj = Info[<span class="string">"lkdfsj"</span>];</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> slkfaj = Info[<span class="string">"slkfaj"</span>];</span><br><span class="line"></span><br><span class="line"> rspMap[<span class="string">"alwkjef"</span>] = alwkjef;</span><br><span class="line"> rspMap[<span class="string">"lkdfsj"</span>] = lkdfsj;</span><br><span class="line"> rspMap[<span class="string">"slkfaj"</span>] = slkfaj;</span><br><span class="line"> rspMap[<span class="string">"lkajfds"</span>] = lkajfds;</span><br><span class="line"> rspMap[<span class="string">"durationss"</span>] = durationss;</span><br><span class="line"> rspMap[<span class="string">"usercount"</span>] = usercount;</span><br><span class="line"> rspMap[<span class="string">"_info"</span>] = rspMap;</span><br><span class="line"> rspMap[<span class="string">"_state"</span>] = state;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ...省略好多代码</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>这些其实都是”认真”二字可以cover的问题,但问题在于,开发阶段难免遇到各端需求变更、后台修改、接口意图变化、功能扩展等,以上痛点代码均有一个特点,如果涉及以上几点变更,都需要更改多处代码,有些是IDE无法提供帮助的,手动修改时极易出错,而且麻烦,懒才是第一生产力,于是要想办法解决这些问题。</p>
<h2 id="优化后代码示例"><a href="#优化后代码示例" class="headerlink" title="优化后代码示例"></a>优化后代码示例</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NemoAppDemo</span> :</span> <span class="keyword">public</span> API</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 自动展开 完成声明与注册</span></span><br><span class="line"> <span class="comment">// clang-format off</span></span><br><span class="line"> NEMO_MACRO_ROUTE_API(NemoAppDemo, demo_api, api1,api2,api3,api4,api5,api6);</span><br><span class="line"> <span class="comment">// clang-format on</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// clang-format off</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">user</span> {</span></span><br><span class="line"> <span class="keyword">int64_t</span> uid; </span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> name; </span><br><span class="line"> <span class="keyword">int32_t</span> role; </span><br><span class="line"> <span class="keyword">int32_t</span> status; </span><br><span class="line"> <span class="keyword">int32_t</span> param1; </span><br><span class="line"> <span class="keyword">int32_t</span> param2; </span><br><span class="line"> <span class="keyword">int32_t</span> param3; </span><br><span class="line"> <span class="comment">//..</span></span><br><span class="line"> <span class="comment">// 自动展开 完成json与C++结构体互相转换、 构造回包等</span></span><br><span class="line"> NEMO_STRUCT_PARSER(uid ,name ,role ,status ,param1 ,param2 ,param3 );</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// clang-format on</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="宏展开"><a href="#宏展开" class="headerlink" title="宏展开"></a>宏展开</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NemoAppDemo</span> :</span> <span class="keyword">public</span> API</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">RegisterRouterFunctions</span><span class="params">(<span class="keyword">void</span>)</span> <span class="keyword">override</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api1"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api1, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api2"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api2, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api3"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api3, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api4"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api4, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api5"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api5, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> RegisterRouterFunction(<span class="string">"/demo_api/api6"</span>, <span class="built_in">std</span>::bind(&NemoAppDemo::api6, <span class="keyword">this</span>, _1, _2));</span><br><span class="line"> ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api1</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api2</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api3</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api4</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api5</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">api6</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& api, <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& params)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">user</span> {</span></span><br><span class="line"> <span class="keyword">int64_t</span> uid;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> name;</span><br><span class="line"> <span class="keyword">int32_t</span> role;</span><br><span class="line"> <span class="keyword">int32_t</span> status;</span><br><span class="line"> <span class="keyword">int32_t</span> param1;</span><br><span class="line"> <span class="keyword">int32_t</span> param2;</span><br><span class="line"> <span class="keyword">int32_t</span> param3;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">FromJson</span><span class="params">(<span class="keyword">const</span> ZegoJsonParser& json)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">bool</span> changed = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"uid"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(uid) tmpVar = (<span class="keyword">decltype</span>(uid))(json[<span class="string">"uid"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != uid) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> uid = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"name"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(name) tmpVar = (<span class="keyword">decltype</span>(name))(json[<span class="string">"name"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != name) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> name = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"role"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(role) tmpVar = (<span class="keyword">decltype</span>(role))(json[<span class="string">"role"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != role) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> role = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"status"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(status) tmpVar = (<span class="keyword">decltype</span>(status))(json[<span class="string">"status"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != status) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> status = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"param1"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(param1) tmpVar = (<span class="keyword">decltype</span>(param1))(json[<span class="string">"param1"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != param1) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> param1 = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"param2"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(param2) tmpVar = (<span class="keyword">decltype</span>(param2))(json[<span class="string">"param2"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != param2) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> param2 = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(<span class="string">"param3"</span>)) {</span><br><span class="line"> <span class="keyword">decltype</span>(param3) tmpVar = (<span class="keyword">decltype</span>(param3))(json[<span class="string">"param3"</span>]); <span class="comment">//这里使用重载的转换函数</span></span><br><span class="line"> <span class="keyword">if</span> (tmpVar != param3) {</span><br><span class="line"> changed |= <span class="literal">true</span>;</span><br><span class="line"> param3 = tmpVar;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> changed;</span><br><span class="line"> };</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">ToMap</span><span class="params">(Poco::VariantMap& mapParams)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> mapParams[<span class="string">"uid"</span>] = uid;</span><br><span class="line"> mapParams[<span class="string">"name"</span>] = name;</span><br><span class="line"> mapParams[<span class="string">"role"</span>] = role;</span><br><span class="line"> mapParams[<span class="string">"status"</span>] = status;</span><br><span class="line"> mapParams[<span class="string">"param1"</span>] = param1;</span><br><span class="line"> mapParams[<span class="string">"param2"</span>] = param2;</span><br><span class="line"> mapParams[<span class="string">"param3"</span>] = param3;</span><br><span class="line"> ;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">Poco::VariantMap <span class="title">ToMap</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> Poco::VariantMap mapParams;</span><br><span class="line"> mapParams[<span class="string">"uid"</span>] = uid;</span><br><span class="line"> mapParams[<span class="string">"name"</span>] = name;</span><br><span class="line"> mapParams[<span class="string">"role"</span>] = role;</span><br><span class="line"> mapParams[<span class="string">"status"</span>] = status;</span><br><span class="line"> mapParams[<span class="string">"param1"</span>] = param1;</span><br><span class="line"> mapParams[<span class="string">"param2"</span>] = param2;</span><br><span class="line"> mapParams[<span class="string">"param3"</span>] = param3;</span><br><span class="line"> ;</span><br><span class="line"> <span class="keyword">return</span> mapParams;</span><br><span class="line"> };</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h2 id="宏编程开发需求"><a href="#宏编程开发需求" class="headerlink" title="宏编程开发需求"></a>宏编程开发需求</h2><p>回顾使用方法 </p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NemoAppDemo</span> :</span> <span class="keyword">public</span> API</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 自动展开 完成声明与注册</span></span><br><span class="line"> <span class="comment">// clang-format off</span></span><br><span class="line"> NEMO_MACRO_ROUTE_API(NemoAppDemo , demo_api , api1 , api2 , api3 , api4 , api5 , api6);</span><br><span class="line"> <span class="comment">// clang-format on</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// clang-format off</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">user</span> {</span></span><br><span class="line"> <span class="keyword">int64_t</span> uid; </span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> name; </span><br><span class="line"> <span class="keyword">int32_t</span> role; </span><br><span class="line"> <span class="keyword">int32_t</span> status; </span><br><span class="line"> <span class="keyword">int32_t</span> param1; </span><br><span class="line"> <span class="keyword">int32_t</span> param2; </span><br><span class="line"> <span class="keyword">int32_t</span> param3; </span><br><span class="line"> <span class="comment">//..</span></span><br><span class="line"> <span class="comment">// 自动展开 完成json与C++结构体互相转换、 构造回包等</span></span><br><span class="line"> NEMO_STRUCT_PARSER(uid , name , role , status , param1 , param2 , param3 );</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// clang-format on</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol>
<li>增加api时,向NEMO_MACRO_ROUTE_API的括号里直接塞就好</li>
<li>增加属性字段更改结构体时,直接添加到NEMO_STRUCT_PARSER的参数中</li>
</ol>
<p>所以我们要实现的基本功能为: 遍历变长宏参数 进行统一的特殊处理</p>
<p>由此需求出发,依赖很多的小技巧和手法,最终完成开发并适配各平台。</p>
<h1 id="技术要点描述"><a href="#技术要点描述" class="headerlink" title="技术要点描述"></a>技术要点描述</h1><h2 id="预处理基本原理"><a href="#预处理基本原理" class="headerlink" title="预处理基本原理"></a>预处理基本原理</h2><p>预处理基本步骤:</p>
<ol>
<li>在进入宏函数前,所有 宏参数 会先进行一次 预扫描 (prescan),完全展开 未用于 拼接标识符 或 获取字面量 的所有参数</li>
<li>在宏函数展开时,用(预扫描展开后的)参数替换 展开目标里的 同名符号</li>
<li>在宏函数展开后,替换后的文本会进行 二次扫描(scan twice),继续展开 结果里出现的宏</li>
</ol>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">s=>start: 开始</span><br><span class="line">DetectMacro=>condition: 是否存在宏</span><br><span class="line">ApplyNumberSymbol=>subroutine: 若有#/## 则拼接标识符 或 获取字面量</span><br><span class="line">GoProcesssPrecan=>operation: 进入预扫描阶段</span><br><span class="line">DoneProcessPrescan=>operation: 预扫描阶段结束</span><br><span class="line">CheckPreParamNumber=>condition: 参数个数是否匹配</span><br><span class="line">CheckPostParamNumber=>condition: 参数个数是否匹配</span><br><span class="line">CheckParam=>condition: 宏参数是否用于 #/##</span><br><span class="line">ParamExpend=>subroutine: 宏实参参数 按此流程展开</span><br><span class="line">ParamReplace=>operation: 宏展开 参数符号替换</span><br><span class="line">success=>end: 成功</span><br><span class="line">error=>end: 失败</span><br><span class="line"></span><br><span class="line">s->ApplyNumberSymbol->DetectMacro</span><br><span class="line">DetectMacro(no)->success</span><br><span class="line">DetectMacro(yes)->CheckPreParamNumber</span><br><span class="line">CheckPreParamNumber(no)->error</span><br><span class="line">CheckPreParamNumber(yes)->GoProcesssPrecan->CheckParam</span><br><span class="line">CheckParam(no)->ParamExpend(right)->CheckPostParamNumber</span><br><span class="line">CheckParam(yes)->DoneProcessPrescan->ParamReplace</span><br><span class="line">CheckPostParamNumber(yes)->DoneProcessPrescan->ParamReplace</span><br><span class="line">CheckPostParamNumber(no)->error</span><br><span class="line">ParamReplace(left)->ApplyNumberSymbol</span><br></pre></td></tr></table></figure>
<p><img data-src="/images/posts/macro/macro01.png" alt="预处理流程图"> </p>
<table>
<thead>
<tr>
<th>步骤</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>ApplyNumberSymbol</td>
<td>拼接标识符 或 获取字面量</td>
</tr>
<tr>
<td>ParamNumberCheck</td>
<td>参数个数是否匹配</td>
</tr>
<tr>
<td>CheckParam</td>
<td>宏参数是否用于 拼接标识符 或 获取字面量</td>
</tr>
<tr>
<td>ParamExpend</td>
<td>宏实参参数展开</td>
</tr>
<tr>
<td>ParamReplace</td>
<td>参数符号替换</td>
</tr>
<tr>
<td>重复</td>
<td>是否仍存在宏 若存在 回到第一步</td>
</tr>
</tbody></table>
<h2 id="惰性求值、延迟调用、延迟展开"><a href="#惰性求值、延迟调用、延迟展开" class="headerlink" title="惰性求值、延迟调用、延迟展开"></a>惰性求值、延迟调用、延迟展开</h2><p>拼接标识符/获取字面量会终止局部宏展开,故需此技术。</p>
<h3 id="举例说明-对预扫描阶段的影响"><a href="#举例说明-对预扫描阶段的影响" class="headerlink" title="举例说明#/##对预扫描阶段的影响"></a>举例说明#/##对预扫描阶段的影响</h3><figure class="highlight gauss"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> FOO(SYMBOL) foo_ ## SYMBOL</span></span><br><span class="line"><span class="built_in">FOO</span>(<span class="built_in">bar</span>) <span class="comment">// -> foo_bar it's ok </span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BAR() bar</span></span><br><span class="line"><span class="built_in">FOO</span>(<span class="built_in">BAR</span>()) <span class="comment">// -> foo_BAR() not foo_bar</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 先检查参数 FOO(BAR()) -> foo_ ## BAR() 该参数用于拼接标识符/获取字面量 不允许展开</span></span><br><span class="line"><span class="comment">// 2. 宏展开 foo_ ## BAR() </span></span><br><span class="line"><span class="comment">// 3. 应用## 合并为 foo_BAR()</span></span><br></pre></td></tr></table></figure>
<p>即:拼接标识符/获取字面量会终止局部宏展开</p>
<p>解决方法: 延迟展开</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><span class="line"><span class="comment">#define FOOimpl(SYMBOL) foo_ ## SYMBOL</span></span><br><span class="line"><span class="comment">#define FOO(SYMBOL) FOOimpl(SYMBOL)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#define BAR() bar</span></span><br><span class="line">FOO(BAR()) </span><br><span class="line"> <span class="regexp">//</span> <span class="number">1</span>. 先检查参数 FOO(BAR()) -> FOOimpl(SYMBOL) 并未用于拼接标识符 或 获取字面量 允许展开</span><br><span class="line"> <span class="regexp">//</span> <span class="number">2</span>. 宏实参参数展开 FOO(bar) </span><br><span class="line"> <span class="regexp">//</span> <span class="number">3</span>. 宏展开 FOOimpl(bar)</span><br><span class="line"> <span class="regexp">//</span> <span class="number">4</span>. 再次扫描 展开为 foo_ <span class="comment">## bar</span></span><br><span class="line"> <span class="regexp">//</span> <span class="number">5</span>. 拼接标识符 最终展开为foo_bar</span><br></pre></td></tr></table></figure>
<h3 id="其他形式的延迟调用、惰性求值等"><a href="#其他形式的延迟调用、惰性求值等" class="headerlink" title="其他形式的延迟调用、惰性求值等"></a>其他形式的延迟调用、惰性求值等</h3><p>第一次宏展开 先返回宏函数, 拼接后 延迟调用</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N(N, ...) NEMO_MACRO_CONCAT(NEMO_MACRO_GET_N_, N)(__VA_ARGS__)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_0(_0, ...) _0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_1(_0, _1, ...) _1</span></span><br></pre></td></tr></table></figure>
<p>元组形式的延迟调用 (涉及宏编程较为复杂的元组打包、元组解包等)</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_APPLY(F, M) NEMO_MACRO_APPLYimpl(F, M)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_APPLYimpl(F, M) F M</span></span><br><span class="line"><span class="comment">// example</span></span><br><span class="line">NEMO_MACRO_APPLY(some_function, (a,b,c,d) )</span><br></pre></td></tr></table></figure>
<p>处理特殊输入</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> FOO(T) int foo(T t)</span></span><br><span class="line">FOO(<span class="built_in">std</span>::<span class="built_in">map</span><a,b>) <span class="comment">// error too many params</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> REMOVE_PARENSimpl(...) __VA_ARGS__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> REMOVE_PARENS(T) REMOVE_PARENSimpl T</span></span><br><span class="line"></span><br><span class="line">FOO(REMOVE_PARENS(<span class="built_in">std</span>::<span class="built_in">map</span><a,b>)) </span><br><span class="line"><span class="comment">// 1. 预扫描 FOO(REMOVE_PARENSimpl (std::map<a,b>)) </span></span><br><span class="line"><span class="comment">// 2. 宏展开 int foo(REMOVE_PARENSimpl (std::map<a,b>)) t)</span></span><br><span class="line"><span class="comment">// 3. 再次展开 int foo(std::map<a,b> t) </span></span><br></pre></td></tr></table></figure>
<h2 id="下标访问"><a href="#下标访问" class="headerlink" title="下标访问"></a>下标访问</h2><p>既然要遍历所有参数,我们首先需要一个 “获取特定参数的宏——下标访问”</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// USAGE::</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_N(0,a,b,c) will expend to a</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_N(1,a,b,c) will expend to b</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_N(2,a,b,c) will expend to c</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_TUPLE_N(0,(a,b,c)) will expend to a</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_TUPLE_N(1,(a,b,c)) will expend to b</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_GET_TUPLE_N(2,(a,b,c)) will expend to c</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N(N, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_CONCAT(NEMO_MACRO_GET_N_, N)(__VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_TUPLE_N(N, T) NEMO_MACRO_APPLY(NEMO_MACRO_GET_N, (N, NEMO_MACRO_REMOVE_PARENS(T)))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_0(_0, ...) _0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_1(_0, _1, ...) _1</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_2(_0, _1, _2, ...) _2</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_3(_0, _1, _2, _3, ...) _3</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_4(_0, _1, _2, _3, _4, ...) _4</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_5(_0, _1, _2, _3, _4, _5, ...) _5</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_GET_N_6(_0, _1, _2, _3, _4, _5, _6, ...) _6</span></span><br></pre></td></tr></table></figure>
<h2 id="长度计算"><a href="#长度计算" class="headerlink" title="长度计算"></a>长度计算</h2><p>下标访问前,我们需要获取宏参数长度</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="comment">#define NEMO_MACRO_ARGS_COUNT_IMPL(...) \</span></span><br><span class="line"> <span class="attribute">NEMO_MACRO_GET_N</span>(<span class="number">99</span>, __VA_ARGS__, <span class="number">99</span>, <span class="number">98</span>, <span class="number">97</span>, <span class="number">96</span>, <span class="number">95</span>, <span class="number">94</span>, <span class="number">93</span>, <span class="number">92</span>, <span class="number">91</span>, <span class="number">90</span>, <span class="number">89</span>, <span class="number">88</span>, <span class="number">87</span>, <span class="number">86</span>, <span class="number">85</span>, <span class="number">84</span>, <span class="number">83</span>, <span class="number">82</span>, <span class="number">81</span>, <span class="number">80</span>, <span class="number">79</span>, <span class="number">78</span>, \</span><br><span class="line"> <span class="attribute">77</span>, <span class="number">76</span>, <span class="number">75</span>, <span class="number">74</span>, <span class="number">73</span>, <span class="number">72</span>, <span class="number">71</span>, <span class="number">70</span>, <span class="number">69</span>, <span class="number">68</span>, <span class="number">67</span>, <span class="number">66</span>, <span class="number">65</span>, <span class="number">64</span>, <span class="number">63</span>, <span class="number">62</span>, <span class="number">61</span>, <span class="number">60</span>, <span class="number">59</span>, <span class="number">58</span>, <span class="number">57</span>, <span class="number">56</span>, <span class="number">55</span>, <span class="number">54</span>, <span class="number">53</span>, <span class="number">52</span>, <span class="number">51</span>, <span class="number">50</span>, <span class="number">49</span>, <span class="number">48</span>, \</span><br><span class="line"> <span class="attribute">47</span>, <span class="number">46</span>, <span class="number">45</span>, <span class="number">44</span>, <span class="number">43</span>, <span class="number">42</span>, <span class="number">41</span>, <span class="number">40</span>, <span class="number">39</span>, <span class="number">38</span>, <span class="number">37</span>, <span class="number">36</span>, <span class="number">35</span>, <span class="number">34</span>, <span class="number">33</span>, <span class="number">32</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">29</span>, <span class="number">28</span>, <span class="number">27</span>, <span class="number">26</span>, <span class="number">25</span>, <span class="number">24</span>, <span class="number">23</span>, <span class="number">22</span>, <span class="number">21</span>, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, \</span><br><span class="line"> <span class="attribute">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>)</span><br></pre></td></tr></table></figure>
<p>很有趣的小技巧,宏编程经常用这种东西,称为穷举/遍历,这种手法会有参数个数限制,但一般不需要支持太多的参数 99足够。</p>
<p>__VA_ARGS__展开后 </p>
<ol>
<li>若__VA_ARGS__有一个参数 GET_N会得到1</li>
<li>若__VA_ARGS__有2个参数 GET_N会得到2</li>
</ol>
<p>完成长度计算</p>
<p>虽然NEMO_MACRO_ARGS_COUNT_IMPL看上去很美好,但当__VA_ARGS__参数个数为0时,会展开为<code>NEMO_MACRO_GET_N(99, , 99, 98, 97, 96, ...)</code>, 依然会返回1,于是我们为了完善GET_N,需要实现长度判空, 为了实现长度判空,我们需要一系列宏,如布尔转换与逻辑运算、条件选择等。</p>
<h2 id="布尔转换与逻辑运算"><a href="#布尔转换与逻辑运算" class="headerlink" title="布尔转换与逻辑运算"></a>布尔转换与逻辑运算</h2><p>这里技巧类似 也应用了遍历与穷举</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// USAGE::</span></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="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_BOOL(N) NEMO_MACRO_GET_N(N, NEMO_MACRO_BOOL_TABLE())</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_BOOL_TABLE() \</span></span><br><span class="line"> <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, \</span><br><span class="line"> <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, \</span><br><span class="line"> <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// N 最大为99 仅预编译时使用</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_NOT(N) NEMO_MACRO_GET_N(N, NEMO_MACRO_NOT_TABLE())</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_NOT_TABLE() \</span></span><br><span class="line"> <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, \</span><br><span class="line"> <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, \</span><br><span class="line"> <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND(A, B) NEMO_MACRO_AND_IMPL(NEMO_MACRO_BOOL(A), NEMO_MACRO_BOOL(B))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND_IMPL(A, B) NEMO_MACRO_CONCAT(NEMO_MACRO_AND_, NEMO_MACRO_CONCAT(A, B))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND_00 0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND_01 0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND_10 0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_AND_11 1</span></span><br></pre></td></tr></table></figure>
<h2 id="条件选择"><a href="#条件选择" class="headerlink" title="条件选择"></a>条件选择</h2><p>使用拼接与二阶段展开实现条件选择</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// USAGE::</span></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="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_IF(PRED, THEN, ELSE) NEMO_MACRO_CONCAT(NEMO_MACRO_IF_, NEMO_MACRO_BOOL(PRED))(THEN, ELSE)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_IF_1(THEN, ELSE) THEN</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_IF_0(THEN, ELSE) ELSE</span></span><br></pre></td></tr></table></figure>
<p>注:宏编程的条件选择中 如果选择了THEN ELSE会从代码中消失 不会有任何编译错误,这其实是个优点,因为使用这个宏时,一般本意是选择性展开,选择THEN时,ELSE如果继续展开大概率展开失败。</p>
<h2 id="逗号判断"><a href="#逗号判断" class="headerlink" title="逗号判断"></a>逗号判断</h2><p>判断宏中是否有逗号</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// USAGE::</span></span><br><span class="line"><span class="comment">// 判断宏中是否有逗号</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_HAS_COMMA(...) \</span></span><br><span class="line"> NEMO_MACRO_EXPAND(NEMO_MACRO_GET_N_100(__VA_ARGS__, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, \</span><br><span class="line"> <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, \</span><br><span class="line"> <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>))</span><br></pre></td></tr></table></figure>
<h2 id="长度判空"><a href="#长度判空" class="headerlink" title="长度判空"></a>长度判空</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// USAGE::</span></span><br><span class="line"><span class="comment">// 长度判空</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY() ;// will expand to -> 1</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(foo) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(foo()) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(()) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(()foo) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(PP_EMPTY) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(NEMO_MACRO_COMMA) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(, ) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(foo, bar) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_IS_EMPTY(, , , ) ;// will expand to -> 0</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// 借助 NEMO_MACRO_GET_N(),我们可以检查 变长参数是否为空:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_IS_EMPTY(...) \</span></span><br><span class="line"> NEMO_MACRO_EXPAND(NEMO_MACRO_AND( \</span><br><span class="line"> NEMO_MACRO_AND(NEMO_MACRO_NOT(NEMO_MACRO_HAS_COMMA(__VA_ARGS__)), NEMO_MACRO_NOT(NEMO_MACRO_HAS_COMMA(__VA_ARGS__()))), \</span><br><span class="line"> NEMO_MACRO_AND(NEMO_MACRO_NOT(NEMO_MACRO_HAS_COMMA(NEMO_MACRO_COMMA_V __VA_ARGS__)), \</span><br><span class="line"> NEMO_MACRO_HAS_COMMA(NEMO_MACRO_COMMA_V __VA_ARGS__()))))</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_COMMA_V(...) ,</span></span><br></pre></td></tr></table></figure>
<h2 id="完善的长度判断"><a href="#完善的长度判断" class="headerlink" title="完善的长度判断"></a>完善的长度判断</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ARGS_COUNT(...) \</span></span><br><span class="line"> NEMO_MACRO_EXPAND(NEMO_MACRO_IF(NEMO_MACRO_IS_EMPTY(__VA_ARGS__), <span class="number">0</span>, NEMO_MACRO_ARGS_COUNT_IMPL(__VA_ARGS__)))</span><br></pre></td></tr></table></figure>
<h2 id="借助以上N点原理,获取解决痛点的核心技术:宏遍历"><a href="#借助以上N点原理,获取解决痛点的核心技术:宏遍历" class="headerlink" title="借助以上N点原理,获取解决痛点的核心技术:宏遍历"></a>借助以上N点原理,获取解决痛点的核心技术:宏遍历</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// clang-format off</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_N</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// USAGE1::</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_N(MACRO_F,param,a,b,c,d,e,f) will expend to MACRO_F(param,a)MACRO_F(param,b)MACRO_F(param,c)MACRO_F(param,d)MACRO_F(param,e)MACRO_F(param,f)</span></span><br><span class="line"><span class="comment">// clang-format on</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N(NEMO_MACRO_N_F, NEMO_MACRO_N_P, ...) \</span></span><br><span class="line"> NEMO_MACRO_EXPAND( \</span><br><span class="line"> NEMO_MACRO_CONCAT(NEMO_MACRO_N_IMPL_, NEMO_MACRO_ARGS_COUNT(__VA_ARGS__))(NEMO_MACRO_N_F, NEMO_MACRO_N_P, __VA_ARGS__))</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL(F, P, M) F(P, M)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_1(F, P, M) NEMO_MACRO_N_IMPL(F, P, M)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_2(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_1(F, P, __VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_3(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_2(F, P, __VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_4(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_3(F, P, __VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_5(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_4(F, P, __VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_6(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_5(F, P, __VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_N_IMPL_7(F, P, M, ...) NEMO_MACRO_EXPAND(NEMO_MACRO_N_IMPL(F, P, M) NEMO_MACRO_N_IMPL_6(F, P, __VA_ARGS__))</span></span><br></pre></td></tr></table></figure>
<h2 id="依赖宏遍历-解决问题"><a href="#依赖宏遍历-解决问题" class="headerlink" title="依赖宏遍历 解决问题"></a>依赖宏遍历 解决问题</h2><p>API处理</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// ROUTE API</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// USAGE::</span></span><br><span class="line"><span class="comment">// NEMO_MACRO_ROUTE_API(className,apiBase,apiA,apiB,apiC,apiD)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// will expend to (magic!)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// public:</span></span><br><span class="line"><span class="comment">// void RegisterRouterFunctions()</span></span><br><span class="line"><span class="comment">// {</span></span><br><span class="line"><span class="comment">// RegisterRouterFunction("/apiBase/apiA" , std::bind(&className::apiA , this , _1 , _2));</span></span><br><span class="line"><span class="comment">// RegisterRouterFunction("/apiBase/apiB" , std::bind(&className::apiB , this , _1 , _2));</span></span><br><span class="line"><span class="comment">// RegisterRouterFunction("/apiBase/apiA" , std::bind(&className::apiA , this , _1 , _2));</span></span><br><span class="line"><span class="comment">// RegisterRouterFunction("/apiBase/apiB" , std::bind(&className::apiB , this , _1 , _2));</span></span><br><span class="line"><span class="comment">// }</span></span><br><span class="line"><span class="comment">// private:</span></span><br><span class="line"><span class="comment">// unsigned int apiA(const std::string& api, const std::string& params);</span></span><br><span class="line"><span class="comment">// unsigned int apiB(const std::string& api, const std::string& params);</span></span><br><span class="line"><span class="comment">// unsigned int apiC(const std::string& api, const std::string& params);</span></span><br><span class="line"><span class="comment">// unsigned int apiD(const std::string& api, const std::string& params);</span></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="comment">//</span></span><br><span class="line"><span class="comment">// clang-format off</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API(className, apiBase, ...) NEMO_MACRO_EXPAND( \</span></span><br><span class="line"> <span class="keyword">public</span> : \</span><br><span class="line"> NEMO_MACRO_ROUTE_API_REG(className, apiBase, __VA_ARGS__) \</span><br><span class="line"> <span class="keyword">private</span> : \</span><br><span class="line"> NEMO_MACRO_ROUTE_API_DEC(className, apiBase, __VA_ARGS__) \</span><br><span class="line"> )</span><br><span class="line"><span class="comment">// register api</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API_REG(className, apiBase, ...) \</span></span><br><span class="line"> void RegisterRouterFunctions(void) override \</span><br><span class="line"> { \</span><br><span class="line"> NEMO_MACRO_N(NEMO_MACRO_ROUTE_API_REG_1, (className,apiBase), __VA_ARGS__); \</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API_REG_1(PACKAGE_className_apiBase, M) \</span></span><br><span class="line"> NEMO_MACRO_APPLY(NEMO_MACRO_ROUTE_API_REG_2,(NEMO_MACRO_REMOVE_PARENS(PACKAGE_className_apiBase),M));</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API_REG_2(className,apiBase,apiName) \</span></span><br><span class="line"> RegisterRouterFunction( NEMO_MACRO_STR(/apiBase/apiName), <span class="built_in">std</span>::bind(&className::apiName, <span class="keyword">this</span>, _1, _2))</span><br><span class="line"><span class="comment">// decl api function</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API_DEC(className, apiBase, ...) \</span></span><br><span class="line"> NEMO_MACRO_N(NEMO_MACRO_ROUTE_API_DEC_1, _, __VA_ARGS__);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_MACRO_ROUTE_API_DEC_1(_, funcName) unsigned int funcName(const std::string& api, const std::string& params);</span></span><br></pre></td></tr></table></figure>
<p>结构体parser</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// NEMO_STRUCT_PARSER</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// USAGE::</span></span><br><span class="line"><span class="comment">// NEMO_STRUCT_PARSER(memberA,memberB,memberC,memberD)</span></span><br><span class="line"><span class="comment">// will expend to</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// public:</span></span><br><span class="line"><span class="comment">// bool FromJson(const NemoJsonParser& json)</span></span><br><span class="line"><span class="comment">// {</span></span><br><span class="line"><span class="comment">// bool changed = false;</span></span><br><span class="line"><span class="comment">// if (json.HasMember("memberA")) {</span></span><br><span class="line"><span class="comment">// decltype(varName) tmpVar = (decltype(memberA))(json["memberA"]);</span></span><br><span class="line"><span class="comment">// if(memberA!=tmpVar){</span></span><br><span class="line"><span class="comment">// changed|=true;</span></span><br><span class="line"><span class="comment">// memberA = tmpVar;</span></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="comment">// if (json.HasMember("memberB")) {</span></span><br><span class="line"><span class="comment">// decltype(varName) tmpVar = (decltype(memberB))(json["memberB"]);</span></span><br><span class="line"><span class="comment">// if(memberB!=tmpVar){</span></span><br><span class="line"><span class="comment">// changed|=true;</span></span><br><span class="line"><span class="comment">// memberB = tmpVar;</span></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="comment">// if (json.HasMember("memberC")) {</span></span><br><span class="line"><span class="comment">// decltype(varName) tmpVar = (decltype(memberC))(json["memberC"]);</span></span><br><span class="line"><span class="comment">// if(memberC!=tmpVar){</span></span><br><span class="line"><span class="comment">// changed|=true;</span></span><br><span class="line"><span class="comment">// memberC = tmpVar;</span></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="comment">// if (json.HasMember("memberD")) {</span></span><br><span class="line"><span class="comment">// decltype(varName) tmpVar = (decltype(memberD))(json["memberD"]);</span></span><br><span class="line"><span class="comment">// if(memberD!=tmpVar){</span></span><br><span class="line"><span class="comment">// changed|=true;</span></span><br><span class="line"><span class="comment">// memberD = tmpVar;</span></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="comment">// return changed;</span></span><br><span class="line"><span class="comment">// };</span></span><br><span class="line"><span class="comment">// void ToMap(Poco::VariantMap& mapParams)</span></span><br><span class="line"><span class="comment">// {</span></span><br><span class="line"><span class="comment">// mapParams["memberA"] = memberA;</span></span><br><span class="line"><span class="comment">// mapParams["memberB"] = memberB;</span></span><br><span class="line"><span class="comment">// mapParams["memberC"] = memberC;</span></span><br><span class="line"><span class="comment">// mapParams["memberD"] = memberD;</span></span><br><span class="line"><span class="comment">// }</span></span><br><span class="line"><span class="comment">// Poco::VariantMap ToMap(void)</span></span><br><span class="line"><span class="comment">// {</span></span><br><span class="line"><span class="comment">// Poco::VariantMap mapParams;</span></span><br><span class="line"><span class="comment">// mapParams["memberA"] = memberA;</span></span><br><span class="line"><span class="comment">// mapParams["memberB"] = memberB;</span></span><br><span class="line"><span class="comment">// mapParams["memberC"] = memberC;</span></span><br><span class="line"><span class="comment">// mapParams["memberD"] = memberD;</span></span><br><span class="line"><span class="comment">// return mapParams;</span></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="comment">//</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_STRUCT_PARSER(...) \</span></span><br><span class="line"> <span class="keyword">public</span>: \</span><br><span class="line"> bool FromJson(const NemoJsonParser& json) \</span><br><span class="line"> { \</span><br><span class="line"> <span class="keyword">bool</span> changed = <span class="literal">false</span>; \</span><br><span class="line"> NEMO_MACRO_N(NEMO_STRUCT_PARSER_FROMJSON, _, __VA_ARGS__); \</span><br><span class="line"> <span class="keyword">return</span> changed; \</span><br><span class="line"> }; \</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">ToMap</span><span class="params">(Poco::VariantMap& mapParams)</span> </span>{ NEMO_MACRO_N(NEMO_STRUCT_PARSER_TOMAP, mapParams, __VA_ARGS__); } \</span><br><span class="line"> Poco::VariantMap ToMap(void) \</span><br><span class="line"> { \</span><br><span class="line"> Poco::VariantMap mapParams; \</span><br><span class="line"> NEMO_MACRO_N(NEMO_STRUCT_PARSER_TOMAP, mapParams, __VA_ARGS__); \</span><br><span class="line"> <span class="keyword">return</span> mapParams; \</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_STRUCT_PARSER_FROMJSON(_, varName) \</span></span><br><span class="line"> <span class="keyword">if</span> (json.HasMember(NEMO_MACRO_STR(varName))) { \</span><br><span class="line"> <span class="keyword">decltype</span>(varName) tmpVar = (<span class="keyword">decltype</span>(varName))(json[NEMO_MACRO_STR(varName)]); \</span><br><span class="line"> <span class="keyword">if</span> (tmpVar != varName) { \</span><br><span class="line"> changed |= <span class="literal">true</span>; \</span><br><span class="line"> varName = tmpVar; \</span><br><span class="line"> } \</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NEMO_STRUCT_PARSER_TOMAP(mapParams, varName) mapParams[NEMO_MACRO_STR(varName)] = varName;</span></span><br></pre></td></tr></table></figure>
<h1 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h1><h2 id="WINDOWS的预处理BUG1"><a href="#WINDOWS的预处理BUG1" class="headerlink" title="WINDOWS的预处理BUG1"></a>WINDOWS的预处理BUG1</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CONCATimpl(a,b) a##b</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CONCAT(a,b) CONCATimpl(a,b) </span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> AA</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BB </span></span><br><span class="line">CONCAT(B, CONCAT(A, A B)) </span><br><span class="line"><span class="comment">// 1. 预扫描 CONCAT(B, AA B))</span></span><br><span class="line"><span class="comment">// 2. 宏展开 CONCAT(B, AA B))</span></span><br><span class="line"><span class="comment">// 3. 预扫描 CONCAT(B, B))</span></span><br><span class="line"><span class="comment">// 4. 宏展开 BB</span></span><br><span class="line"><span class="comment">// 5. 宏展开 </span></span><br><span class="line"></span><br><span class="line"><span class="comment">// --> should be empyt</span></span><br><span class="line"><span class="comment">// bug msvc get</span></span><br><span class="line"> BAA B</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>但MSVC会获得 BAA B<br>当同名迭代调用时,且最终步骤带有#,则可能触发该bug。规避方法 使用延迟调用。</p>
<p>NEMO_MACRO_APPLY(CONCAT,(B, CONCAT(A, A B)))</p>
<p>see <a href="https://stackoverflow.com/questions/62363585/why-is-msvc-preprocessor-concatenating-tokens-differently-than-gcc-and-clang?rq=1">https://stackoverflow.com/questions/62363585/why-is-msvc-preprocessor-concatenating-tokens-differently-than-gcc-and-clang?rq=1</a></p>
<h2 id="WINDOWS的预处理BUG2-VA-ARGS-展开失败"><a href="#WINDOWS的预处理BUG2-VA-ARGS-展开失败" class="headerlink" title="WINDOWS的预处理BUG2 __VA_ARGS__展开失败"></a>WINDOWS的预处理BUG2 __VA_ARGS__展开失败</h2><p>在传统预处理器中,如果宏将其参数之一转发给另一个依赖宏,则在插入时,该参数不会 “解压缩”<br>see <a href="https://docs.microsoft.com/zh-cn/cpp/preprocessor/preprocessor-experimental-overview?view=vs-2019">https://docs.microsoft.com/zh-cn/cpp/preprocessor/preprocessor-experimental-overview?view=vs-2019</a></p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADD2impl(a1, a2) (a1)+(a2)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADD2(...) ADD2impl(__VA_ARGS__)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, ADD2(<span class="number">1</span>, <span class="number">2</span>));</span><br><span class="line"><span class="comment">// should be printf("%d\n",1+2);</span></span><br><span class="line"><span class="comment">// msvc get printf("%d\n",(1,2) +());</span></span><br></pre></td></tr></table></figure>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> F(x, ...) X = x and VA_ARGS = __VA_ARGS__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> G(...) F(__VA_ARGS__)</span></span><br><span class="line">F(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line">G(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line"><span class="comment">// F and G all should be </span></span><br><span class="line"> X = <span class="number">1</span> <span class="keyword">and</span> VA_ARGS = <span class="number">2</span>, <span class="number">3</span>;</span><br><span class="line"> X = <span class="number">1</span> <span class="keyword">and</span> VA_ARGS = <span class="number">2</span>, <span class="number">3</span>;</span><br><span class="line"><span class="comment">// bug msvc get</span></span><br><span class="line"> X = <span class="number">1</span> <span class="keyword">and</span> VA_ARGS = <span class="number">2</span>, <span class="number">3</span>;</span><br><span class="line"> X = <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> <span class="keyword">and</span> VA_ARGS = ;</span><br></pre></td></tr></table></figure>
<p>MSVC会将__VA_ARGS__视为单参数</p>
<figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">msvc</span><br><span class="line"> G(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line"> F(__V<span class="built_in">A_ARGS</span>__)</span><br><span class="line"> X = x <span class="literal">and</span> V<span class="built_in">A_ARGS</span> = __V<span class="built_in">A_ARGS</span>__</span><br></pre></td></tr></table></figure>
<p>规避方法: 但凡参数列表中有… 需要增加中间层 使其提前展开!</p>
<figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="selector-id">#define</span> <span class="selector-tag">NEMO_MACRO_EXPAND</span>(...) <span class="selector-tag">__VA_ARGS__</span></span><br><span class="line"><span class="selector-id">#define</span> <span class="selector-tag">G</span>(...) <span class="selector-tag">NEMO_MACRO_EXPAND</span>(F(__VA_ARGS__));</span><br><span class="line"> <span class="selector-tag">G</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>) <span class="selector-tag">is</span> <span class="selector-tag">OK</span></span><br></pre></td></tr></table></figure>
<p>如果没有NEMO_MACRO_EXPAND<br>MSVC会将其当作单参数</p>
<p>see <a href="https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly">https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly</a></p>
<h2 id="MSVC-BUG-3-重新扫描算法不符合标准"><a href="#MSVC-BUG-3-重新扫描算法不符合标准" class="headerlink" title="MSVC BUG 3 重新扫描算法不符合标准"></a>MSVC BUG 3 重新扫描算法不符合标准</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> REMOVE_PATTEN(...) __VA_ARGS__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> IMPL1(prefix,value) do_thing_one( prefix, value)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> IMPL2(prefix,value) do_thing_two( prefix, value)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DO_THING(macro_switch, b) CAT(IMPL, macro_switch) ECHO(( <span class="meta-string">"Hello"</span>, b))</span></span><br><span class="line"></span><br><span class="line">DO_THING(<span class="number">1</span>, <span class="string">"World"</span>); <span class="comment">//-> do_thing_one("hello","world") or IMPL1 ( "Hello","World");</span></span><br><span class="line">DO_THING(<span class="number">2</span>, <span class="string">"World"</span>); <span class="comment">//-> do_thing_two("hello","world") or IMPL1 ( "Hello","World");</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// msvc</span></span><br><span class="line"> <span class="comment">//-> do_thing_one("hello","world") </span></span><br><span class="line"> <span class="comment">//-> do_thing_two("hello","world")</span></span><br><span class="line"><span class="comment">// gcc clang (符合标准)</span></span><br><span class="line"> <span class="comment">//->IMPL1 ( "Hello","World");</span></span><br><span class="line"> <span class="comment">//->IMPL1 ( "Hello","World");</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// DO_THING(1, "World") 扩展到 CAT(IMPL, 1) ECHO(("Hello", "World"))</span></span><br><span class="line"><span class="comment">// CAT(IMPL, 1) 展开到 IMPL ## 1 ,将扩展到 IMPL1</span></span><br><span class="line"><span class="comment">// 现在,令牌处于以下状态: IMPL1 ECHO(("Hello", "World"))</span></span><br><span class="line"><span class="comment">// 预处理器查找类似函数的宏标识符 IMPL1 。 由于后面不是 ( ,因此不会将其视为类似于函数的宏调用。</span></span><br><span class="line"><span class="comment">// 预处理器转到以下标记。 它将查找调用类似函数的宏 ECHO ECHO(("Hello", "World")) ,该宏将扩展到 ("Hello", "World")</span></span><br><span class="line"><span class="comment">// IMPL1 永远不会被视为展开,因此扩展的完整结果为: IMPL1("Hello", "World");</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>解决方法 延迟展开,使其IMPL1展开时 后边是( 即可顺利展开<br><code>#define DO_THING(macro_switch, b) APPLY(CAT(IMPL, macro_switch),ECHO(( "Hello", b))</code></p>
<h2 id="CLANG、MSVC-VA-ARGS-0参数BUG"><a href="#CLANG、MSVC-VA-ARGS-0参数BUG" class="headerlink" title="CLANG、MSVC VA_ARGS 0参数BUG"></a>CLANG、MSVC <strong>VA_ARGS</strong> 0参数BUG</h2><p>先了解<code>##__VA_ARGS__与__VA_ARGS__</code>的区别</p>
<figure class="highlight sas"><table><tr><td class="code"><pre><span class="line">log(<span class="meta">format</span>,...) c<span class="meta">clog(</span><span class="meta">format</span>,__VA_ARGS__); | ==> c<span class="meta">clog(</span><span class="meta">format</span>,); | 编译出错</span><br><span class="line">log(<span class="meta">format</span>,...) c<span class="meta">clog(</span><span class="meta">format</span>,##__VA_ARGS__); | ==> c<span class="meta">clog(</span><span class="meta">format</span>); | pass</span><br></pre></td></tr></table></figure>
<p><code>##__VA_ARGS__</code>并非C/C++标准中的东西 是编译器扩展:当参数长度为0时,吃掉前面的逗号,而标准中并未说明__VA_ARGS__会eat逗号</p>
<p>但实际上 这里只有gcc是符合标准的 clang、MSVC 无论哪个宏,都会eat逗号。</p>
<p>这个bug在判断参数长度、判断参数是否有逗号等情况时会遇到。</p>
<h2 id="GCC安卓-奇怪的换行BUG"><a href="#GCC安卓-奇怪的换行BUG" class="headerlink" title="GCC安卓 奇怪的换行BUG"></a>GCC安卓 奇怪的换行BUG</h2><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">NEMO_MACRO_ROUTE_API(NemoAppMeetingRoom, demo_api, login, set_something, get_something, api1, api2); <span class="regexp">//</span> is OK</span><br><span class="line"></span><br><span class="line"><span class="regexp">//</span> 代码太长 换个行吧</span><br><span class="line">NEMO_MACRO_ROUTE_API(NemoAppMeetingRoom, demo_api, login, set_something, get_something, </span><br><span class="line"> api1, api2);</span><br><span class="line"><span class="regexp">//</span> WT? 找不到 api1 api2</span><br></pre></td></tr></table></figure>
<p>总之很奇怪,换行的话 第二行的东西会消失,神奇的bug,无奈只能<code>clang-format off</code> 强行一行</p>
<h2 id="MSVC-BUG-FIX"><a href="#MSVC-BUG-FIX" class="headerlink" title="MSVC BUG-FIX"></a>MSVC BUG-FIX</h2><p>2020年9月10日 MSVC:”我们正在更新 Microsoft c + + 预处理器来改善标准一致性、修复长久持续 bug 并更改正式定义的某些行为。”<br>十几年的bug,终于修复了, 可使用/Zc:preprocessor 启用符合标准的预处理器,但仅限 Visual Studio 2019 版本16.5以上</p>
<p>但预计很长时间以内,大家还是以有bug的msvc来开发复杂宏,以便兼容所有MSVC版本。</p>
<h1 id="其他概念"><a href="#其他概念" class="headerlink" title="其他概念"></a>其他概念</h1><p>宏数据结构 (元组 序列 列表)、符号匹配、递归重入、条件循环、数值比较、数值运算(C可使用 C++用constexpr即可)</p>
<p>参考资料</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">https:<span class="regexp">//</span>stackoverflow.com<span class="regexp">/questions/</span><span class="number">5134523</span>/msvc-doesnt-expand-va-args-correctly</span><br><span class="line">https:<span class="regexp">//</span>stackoverflow.com<span class="regexp">/questions/</span><span class="number">5134523</span>/msvc-doesnt-expand-va-args-correctly</span><br><span class="line">https:<span class="regexp">//</span>stackoverflow.com<span class="regexp">/questions/</span><span class="number">62363585</span>/why-is-msvc-preprocessor-concatenating-tokens-differently-than-gcc-and-clang?rq=<span class="number">1</span></span><br><span class="line">https:<span class="regexp">//</span>zhuanlan.zhihu.com<span class="regexp">/p/</span><span class="number">152354031</span></span><br><span class="line">https:<span class="regexp">//</span>zhuanlan.zhihu.com<span class="regexp">/p/</span><span class="number">59807834</span></span><br><span class="line">https:<span class="regexp">//</span>www.zhihu.com<span class="regexp">/question/</span><span class="number">40698752</span></span><br><span class="line">https:<span class="regexp">//</span>zhuanlan.zhihu.com<span class="regexp">/p/</span><span class="number">152354031</span></span><br><span class="line">https:<span class="regexp">//</span>docs.microsoft.com<span class="regexp">/zh-cn/</span>cpp<span class="regexp">/preprocessor/</span>preprocessor-experimental-overview?view=vs-<span class="number">2019</span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>C++</category>
</categories>
<tags>
<tag>C++</tag>
<tag>宏编程</tag>
</tags>
</entry>
</search>