-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
668 lines (318 loc) · 436 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>RubyKaigi 2024 心得 & 紀錄</title>
<link href="/2024/06/02/RubyKaigi-2024/"/>
<url>/2024/06/02/RubyKaigi-2024/</url>
<content type="html"><![CDATA[<p><img src="image.jpg" alt="image"><br>大家好,我是 Cindy,今年第一次參加日本的 RubyKaigi,第一次感覺研討會原來可以有慶典的感覺,覺得是很棒的體驗,所以決定記錄下來 (防止金魚腦的我忘記),也希望大家看到我的分享也會有興趣有生之年可以體驗看看,這有趣的活動。</p><p>我在 <a href="https://www.facebook.com/RubyJamTW/" target="_blank" rel="noopener">Ruby Jam</a> 有分享 RubyKaigi,所以做了<a href="/pdf/rubykaigi2024_cindyliu.pdf">簡報</a>想看的人可以下載看看,下面內容也會大部分跟簡報一致,可能多一點簡報放不下的。</p><h2 id="目標"><a href="#目標" class="headerlink" title="目標"></a>目標</h2><p>參加 RubyKaigi 大家可以自己設定自己的目標,我只是很單純的想要體驗 Ruby 日本社群,可能其他人有其他目標,像是可以見到 Ruby Committer,跟大神們講講話之類的,聽大神們現場講議程,等等,都很棒。</p><h2 id="事前攻略"><a href="#事前攻略" class="headerlink" title="事前攻略"></a>事前攻略</h2><h3 id="要做的事"><a href="#要做的事" class="headerlink" title="要做的事"></a>要做的事</h3><ul><li>報名 <a href="https://rubykaigi.org" target="_blank" rel="noopener">RubyKaigi</a></li><li>報名活動 (參考<a href="#活動網站">活動網站</a>)</li><li>學日文 (有一定程度可以溝通的話會更好玩)</li><li>有 <a href="https://x.com/cindyliu923" target="_blank" rel="noopener">X</a> 帳號 (大家都會交換 X)</li></ul><p>確定想去的話看到賣票就先買了因為超早鳥票真的便宜,RubyKaigi 除了官方的活動外,各家的贊助商都會舉辦各自的活動,如果想參加就要在事前多看看哪裡有活動,然後要搶報名的名額囉。</p><h3 id="活動網站"><a href="#活動網站" class="headerlink" title="活動網站"></a>活動網站</h3><ul><li>X 搜尋 #rubykaigi<br><a href="https://x.com/search?q=%23rubykaigi&src=typeahead_click&f=live" target="_blank" rel="noopener">https://x.com/search?q=%23rubykaigi&src=typeahead_click&f=live</a></li><li>connpass 搜尋 rubykaigi + 活動期間<br><a href="https://connpass.com/search/?q=rubykaigi&start_from=05%2F14%2F2024&start_to=05%2F17%2F2024&sort=" target="_blank" rel="noopener">https://connpass.com/search/?q=rubykaigi&start_from=05%2F14%2F2024&start_to=05%2F17%2F2024&sort=</a></li><li>官網/年份/events<br><a href="https://rubykaigi.org/2024/events/" target="_blank" rel="noopener">https://rubykaigi.org/2024/events/</a></li></ul><p>在 X 上很多人會分享活動,所以 X 上會是很棒的搜尋站囉,另外跟我一起去的 <a href="https://x.com/k_hno3" target="_blank" rel="noopener">Kasa</a> 小夥伴還做了 iphone 可以跳出 connpass 有活動的提醒通知小工具,厲害!官網的活動則是會比較晚才有,有些比較早跑的就可能會報不到,但比較晚出現的就沒問題,不只晚上有活動中午也有活動 (不確定是不是因為今年的便當是限量的所以有些贊助商就舉辦了午餐的活動),另外除了上面列的之外 <a href="https://www.doorkeeper.jp/" target="_blank" rel="noopener">Doorkeeper</a> 也是有活動的網站,我們有報名到女子晚餐就是從這個網站報名到的囉。</p><h2 id="參加的活動"><a href="#參加的活動" class="headerlink" title="參加的活動"></a>參加的活動</h2><p>接下來跟大家介紹我有參加的活動</p><ul><li><p><a href="https://tokyodev.doorkeeper.jp/events/172303" target="_blank" rel="noopener">TokyoDev x RailsGirls x WNB.rb: Women and Non-binary Dinner&DrinkUp!</a><br>RubyKaigi 前一天晚上的女子晚餐,活動單位舉辦了一些前一天的活動,可以讓第一次參加的朋友在前一天就先認識一些人,真的很貼心,我還在這個晚餐認識了會中文的日本人,覺得好親切啊:)</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Day0のTokyoDev x RailsGirls x WNB.rb: Women and Non-binary DrinkUp! みんな来てくれて本当にありがとうございます!すごく楽しかった!RubyKaigiの3日、よろしくお願いします!<br><br>Thank you so much for coming! So happy to see so many making time for this gathering!! Let’s enjoy <a href="https://twitter.com/hashtag/RubyKaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#RubyKaigi</a>! <a href="https://t.co/6uq2g8DMv2" target="_blank" rel="noopener">pic.twitter.com/6uq2g8DMv2</a></p>— mish (@mishmashtan) <a href="https://twitter.com/mishmashtan/status/1790402951695221174?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 14, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li><li><p><a href="https://coubic.com/storesinc/3574197" target="_blank" rel="noopener">STORES CAFE for Women (Day 1)</a><br>第一天的女子午餐,中間還讓大家交換位子,不愧是很會辦活動的日本人,覺得尊敬,總是會顧慮到小細節,像是我跟我朋友是外國人,所以讓我們坐一起其他人換位子和我們聊天,雖然最後吃得有點趕,但總體而言是很棒的午餐,認識了日本女工程師外,還認識了 <a href="https://rubykaigi.org/2024/presentations/coe401_.html#day1" target="_blank" rel="noopener">An adventure of Happy Eyeballs</a> 演講的講者,最後還收了主辦給的小禮物:)</p></li><li><p><a href="https://mybest.connpass.com/event/316046/" target="_blank" rel="noopener">Yuntaku Night</a><br>第一天的晚餐,晚餐好吃,最特別的是沖繩傳統三味線表演,很好玩。表演者還叫大家一起喊 iyasasa (雖然我還問說這是什麼意思,讓大家困擾了一下,真是不好意思),最後大家還很配合地站起來跳舞,有趣。據說 mybest 說是也有架台灣的<a href="https://tw.my-best.com/" target="_blank" rel="noopener">網站</a>。</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E3%82%86%E3%82%93%E3%81%9F%E3%81%8FNight?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#ゆんたくNight</a> お越しの皆様ありがとうございました!たくさんの方にお越しいただけて嬉しかったです!楽しかったですね🙌<br>明日もお会いできること楽しみにしています〜!!<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://twitter.com/hashtag/rubykaigi2024?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi2024</a> <a href="https://twitter.com/hashtag/mybest?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#mybest</a> <a href="https://t.co/q0ciYs3TbP" target="_blank" rel="noopener">pic.twitter.com/q0ciYs3TbP</a></p>— Satoko Iwasa@mybest (@sachii1015) <a href="https://twitter.com/sachii1015/status/1790757970621735274?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 15, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li><li><p><a href="https://andpad.connpass.com/event/315564/" target="_blank" rel="noopener">Code Party RubyKaigi 2024 Day 2</a><br>第二天的晚餐,參加了 Code Party,沒有參加過這類的活動我很緊張,活動一開始會讓大家選白板上的主題,然後加入有興趣主題的桌子,跟大家一起討論要做什麼。因為覺得 <code>rspec</code> <code>rubocop</code> 是我有在用的所以就選了這張桌子,一開始因為不知道要做什麼所以有點尷尬,後來還好有一位提出了他想要解決的問題,主要是想要部分提高 rspec-openapi gem 的輸出,所以我們 8 個人一起討論研究怎麼做,其中還遇到可能是因為測試程式碼內部啟動了一個單獨的進程,無法使用 <code>binging.irb</code> 或 <code>puts</code> 方法查看內部,後來有人想到可以用 <code>IO.write</code> 的方法把我們想查看的資訊寫在檔案裡,然後因為我日文不太好,大家還很用心地跟我說明發生什麼事情,覺得很感人,讓我有很棒的體驗,謝謝大家。</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">コード懇親会/Code Party<br>rspec-openapi チーム!<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://t.co/DGCTGG9uaE" target="_blank" rel="noopener">pic.twitter.com/DGCTGG9uaE</a></p>— Shuto Tatebayashi / 舘林 秀和 (@shutooike) <a href="https://twitter.com/shutooike/status/1791085503875580007?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li><li><p><a href="https://connpass.com/event/313947/" target="_blank" rel="noopener">RubyKaigi 2024 After Party</a><br>最後一天的晚餐,這次的贊助商是 mov,要先在會場換好飲料券,再換的時候工作人員還說我很快 XD (很前面搶到名額的意思),活動前還有發解酒液 (真的貼心),會場讓人很驚艷 (包了兩層樓好幾間餐廳),基本上是餐廳大家隨便吃,然後要喝飲料或酒再用券去換,其中有些餐廳可以唱歌,所以可以看到大家熱唱的樣子,很好玩:)</p></li><li><p><a href="https://conference.pixiv.co.jp/2024/rubymusicmixin" target="_blank" rel="noopener">RubyMusicMixin 2024</a><br>最後的夜店活動,也很有趣,也是有飲料或酒券,雖然我是沒待滿待好,但去體驗一下也很不錯:)</p><blockquote class="twitter-tweet"><p lang="zh" dir="ltr">認識了日本朋友們,很有趣的體驗!Rubyist 很酷!第一次去夜店XD <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://t.co/IILTDJDAPG" target="_blank" rel="noopener">pic.twitter.com/IILTDJDAPG</a></p>— Cindy Liu (@cindyliu923) <a href="https://twitter.com/cindyliu923/status/1791621124398497844?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 18, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li></ul><h2 id="會場"><a href="#會場" class="headerlink" title="會場"></a>會場</h2><p>(會場內的詳細圖片直接看簡報惹 (懶得再貼一次的我),會場佈置的很可愛,設計風格一致)</p><p>建議大家可以在前一天 checkin,這樣議程開始的第一天可以不用太早起床。如果不想被拍照的人可以拿 White lanyards,又一個貼心的設計。詳細資訊有列在<a href="https://rubykaigi.org/2024/onsite/" target="_blank" rel="noopener">官網</a>上。不會日文也沒關係,有日翻英的即時翻譯,雖然有點延遲,但整體而言還是ok的。會場內的下午茶超激烈,因為人很多,所以如果有人群恐懼可能會有點怕 XD 但我還是有拍到一些照片,有吃到一點點水果跟布丁,除了甜的也有鹹的下午茶,但我沒吃到鹹的就是惹。平常時間的話有飲料可以拿,中午則是有限量的便當。想用電腦做自己事情的人還有一個用電腦區域,另外想徵才的話有個白板大家可以貼上自己公司在徵才的廣告,世界地圖區則是大家貼上自己來自哪個國家的環節,我在那邊捕捉到 Matz (Ruby 程式設計語言的主要設計者和實現者) 惹,太幸運惹。</p> <blockquote class="twitter-tweet"><p lang="zh" dir="ltr">跟 ruby 爸爸的合照!感謝讓我有飯吃!日文聽說突飛猛進中 XD<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://t.co/q5HoLA30mj" target="_blank" rel="noopener">pic.twitter.com/q5HoLA30mj</a></p>— Cindy Liu (@cindyliu923) <a href="https://twitter.com/cindyliu923/status/1790680203133628573?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 15, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h2 id="贊助商攤位"><a href="#贊助商攤位" class="headerlink" title="贊助商攤位"></a>贊助商攤位</h2><p>我覺得每個攤位都可以感覺得到他們的用心,真的很厲害。</p><h3 id="考題類型"><a href="#考題類型" class="headerlink" title="考題類型"></a>考題類型</h3><p>這類的攤位每天都會出題目給大家挑戰,很有趣,適合喜歡思考的工程師,也是其中我最喜歡的類型。</p><ul><li>Findy<br>Findy 的題目,我每天都去做答,三天都答對了超級開心的,主要是考對 Ruby 的熟悉程度,包含版本的變化等等。<ul><li><a href="https://tech.findy.co.jp/entry/2024/05/21/102306" target="_blank" rel="noopener">題目與解答</a></li></ul></li><li>Flatt Security<br>日本資安公司 Flatt Security 每天都出一題跟 yaml 解析有關的問題,主要是 Ruby 跟各家語言解析後是不同的結果,這可能就是一種不一致吧,我前兩天都答錯了,最扯的是第二天的題目<a href="https://x.com/elct9620" target="_blank" rel="noopener">蒼時</a>有跟我說答案我還是答錯不知道在幹嘛,但最後一天有答對就是惹 QQ。<ul><li><a href="https://blog.flatt.tech/entry/rubykaigi_2024" target="_blank" rel="noopener">題目跟解答</a></li></ul></li><li>STORES<br>在女子會認識的工程師拿到了他們攤位的題目,然後我忘記做題目惹,啥眼。不過回台灣之後我有把它做完,但要拿到 1 分對我來說還是有些困難。<ul><li><a href="https://ruby-quiz-2024.storesinc.tech/" target="_blank" rel="noopener">題目網站</a></li><li><a href="https://product.st.inc/entry/2024/05/27/113038" target="_blank" rel="noopener">題目與解答</a></li></ul></li></ul><h3 id="可愛類型"><a href="#可愛類型" class="headerlink" title="可愛類型"></a>可愛類型</h3><p>可愛就讚</p><h3 id="遊戲互動類型"><a href="#遊戲互動類型" class="headerlink" title="遊戲互動類型"></a>遊戲互動類型</h3><p>有貼發票抽獎,或分享抽獎,或幫忙 code review 等等。</p><blockquote class="twitter-tweet"><p lang="zh" dir="ltr">My first Ruby Kaigi was an enriching experience for my mind, heart, and hands. Thank you to everyone I met. I'm eager to learn Japanese! <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> 收穫滿滿 😀 可以感覺到每個人的用心,很棒的活動體驗。 <a href="https://t.co/RXozYhWmOJ" target="_blank" rel="noopener">pic.twitter.com/RXozYhWmOJ</a></p>— Cindy Liu (@cindyliu923) <a href="https://twitter.com/cindyliu923/status/1792103843121250669?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 19, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h2 id="議程"><a href="#議程" class="headerlink" title="議程"></a>議程</h2><p>主要列幾場我有聽的議程 & 沒那麼技術的心得 (如果我有寫錯歡迎大家告訴我唷)</p><h3 id="Writing-Weird-Code"><a href="#Writing-Weird-Code" class="headerlink" title="Writing Weird Code"></a>Writing Weird Code</h3><p>記得前一天晚餐就有人跟我說這場演講的講者很有趣,所以很期待,第一天的 keynote 聽完之後真的覺得是很有意思的演講,寫得很奇怪的程式碼跑出各種神奇的動畫。很多人在 X 分享在車站附近看到的動畫,是用 ruby 做的,也是這場議程的講者的作品。從這場演講可以去反思平常沒有想過的問題,奇怪的問題,也許思考這些可以讓我們更理解 ruby。</p><p>範例程式碼:<a href="https://github.com/tompng/selftrick2024" target="_blank" rel="noopener">https://github.com/tompng/selftrick2024</a></p><blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">県庁前のrubykaigiのムービーFULLバージョン <a href="https://t.co/FfRCRLVCIJ" target="_blank" rel="noopener">pic.twitter.com/FfRCRLVCIJ</a></p>— すぎうり (@uproad3) <a href="https://twitter.com/uproad3/status/1790257572571975877?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 14, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Day1のキーノート Writing Weird Code の発表資料です。<br>My day1 keynote slide <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a><a href="https://t.co/6rBDPjdX7P" target="_blank" rel="noopener">https://t.co/6rBDPjdX7P</a></p>— ぺん! (@tompng) <a href="https://twitter.com/tompng/status/1792482001452425444?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 20, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Strings-Interpolation-Optimisation-amp-Bugs"><a href="#Strings-Interpolation-Optimisation-amp-Bugs" class="headerlink" title="Strings! Interpolation, Optimisation & Bugs"></a>Strings! Interpolation, Optimisation & Bugs</h3><p>這場演講主要講字串在重新分布記憶體的時候出現的 bug,因為平常也不太會去追純 ruby 的執行過程的細節,所以把指令記下來 <code>ruby --dump=insns test.rb</code>,另外發現有人寫關於這場演講的<a href="https://developers.techouse.com/entry/RubyKaigi-2024-eightbitraptor-day1" target="_blank" rel="noopener">文章</a>,還蠻清楚的,可以參考。我覺得這個講者最後的結論蠻好的也記下來了:</p><ul><li>Any Changes can have Unexpected impacts</li><li>Always question your assumptions</li><li>Why was the change important?</li><li>What was being achieved?</li><li>How do we verify?</li></ul><h3 id="Let’s-use-LLMs-from-Ruby-〜-Refine-RBS-types-using-LLM-〜"><a href="#Let’s-use-LLMs-from-Ruby-〜-Refine-RBS-types-using-LLM-〜" class="headerlink" title="Let’s use LLMs from Ruby 〜 Refine RBS types using LLM 〜"></a>Let’s use LLMs from Ruby 〜 Refine RBS types using LLM 〜</h3><p>用 AI 產生 ruby 型別定義的方法,其中講者提到因為覺得有趣就去做了,我覺得很棒就記下來了。</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> の登壇報告ブログ書きました! 補足とかも載せてますー。 RubyKaigi 2024 で RBS と LLM の話をしました|黒曜 <a href="https://t.co/3mTX5yH1KC" target="_blank" rel="noopener">https://t.co/3mTX5yH1KC</a> <a href="https://twitter.com/hashtag/zenn?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#zenn</a></p>— 黒曜@Leaner Technologies (@kokuyouwind) <a href="https://twitter.com/kokuyouwind/status/1792843050580099430?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 21, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Exploring-Reline-Enhancing-Command-Line-Usability"><a href="#Exploring-Reline-Enhancing-Command-Line-Usability" class="headerlink" title="Exploring Reline: Enhancing Command Line Usability"></a>Exploring Reline: Enhancing Command Line Usability</h3><p>介紹 irb 中的 reline,如果大家有覺得 irb 怎麼越來越好用了,可以看看這個演講,其中印象比較深的是 Reline 可以直接編輯 command line 的輸入,以及 undo 的功能。</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Exploring Reline: Enhancing Command Line Usability のスライドです。ありがとうございました! <a href="https://t.co/TRQ9ZAHKJW" target="_blank" rel="noopener">https://t.co/TRQ9ZAHKJW</a> <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://twitter.com/hashtag/rubykaigib?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigib</a></p>— ima1zumi (@ima1zumi) <a href="https://twitter.com/ima1zumi/status/1790650187108765911?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 15, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="An-adventure-of-Happy-Eyeballs"><a href="#An-adventure-of-Happy-Eyeballs" class="headerlink" title="An adventure of Happy Eyeballs"></a>An adventure of Happy Eyeballs</h3><p>介紹 Ruby socket library 中當 IPV6 和 IPV4 同時存在的時候,IPV6 不能連線 IPV4 可以連線,卻繼續等待 IPV6 連線的問題。這場演講厲害的地方在於講者可以將相對困難的議題,用很簡單的圖片加上說明讓大家了解。</p><blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/rubykaigiB?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigiB</a> にてこの後16:40よりお話しする「An adventure of Happy Eyeballs」の発表資料です。<br> This is a slide from my presentation at RubyKaigi 2024. <a href="https://t.co/ACPo6tHI0Z" target="_blank" rel="noopener">https://t.co/ACPo6tHI0Z</a><br> <br>よろしくお願いします! <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a></p>— Misaki Shioi (しおい) (@coe401_) <a href="https://twitter.com/coe401_/status/1790638703851012401?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 15, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Leveraging-Falcon-and-Rails-for-Real-Time-Interactivity"><a href="#Leveraging-Falcon-and-Rails-for-Real-Time-Interactivity" class="headerlink" title="Leveraging Falcon and Rails for Real-Time Interactivity"></a>Leveraging Falcon and Rails for Real-Time Interactivity</h3><p>講者是 ruby async 的作者並創建了 Falcon Web 伺服器,這場演講講了一段互動遊戲發展的歷史,最後也展示了將互動遊戲搬到瀏覽器上讓 Matz 上台玩看看,主要的重點是 server side render 的即時互動遊戲。(感謝<a href="https://x.com/elct9620" target="_blank" rel="noopener">蒼時</a>提醒,上次分享沒有說到 server side 的重點)</p><p>會眾很厲害的筆記:</p><blockquote class="twitter-tweet"><p lang="qme" dir="ltr"><a href="https://twitter.com/hashtag/RubyKaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#RubyKaigi</a><a href="https://t.co/cFPhPc1FZ1" target="_blank" rel="noopener">https://t.co/cFPhPc1FZ1</a> <a href="https://t.co/ODvhpPeRzP" target="_blank" rel="noopener">pic.twitter.com/ODvhpPeRzP</a></p>— りさきゃん🍉 (@_risacan_) <a href="https://twitter.com/_risacan_/status/1790931687989510181?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>範例程式碼: <a href="https://github.com/socketry/lively" target="_blank" rel="noopener">https://github.com/socketry/lively</a></p><p>Matz 上台玩遊戲:</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Matz playing a Flappy Bird written by <a href="https://twitter.com/ioquatix?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@ioquatix</a> in Ruby and running on the server at <a href="https://twitter.com/hashtag/RubyKaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#RubyKaigi</a> <a href="https://t.co/bgiHO3g8iY" target="_blank" rel="noopener">pic.twitter.com/bgiHO3g8iY</a></p>— Benoit Daloze (@eregontp) <a href="https://twitter.com/eregontp/status/1790930428490379758?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>最後我記下了覺得有道理的話:<br>We have made things too complex</p><h3 id="Finding-Memory-Leaks-in-the-Ruby-Ecosystem"><a href="#Finding-Memory-Leaks-in-the-Ruby-Ecosystem" class="headerlink" title="Finding Memory Leaks in the Ruby Ecosystem"></a>Finding Memory Leaks in the Ruby Ecosystem</h3><p>這是一場雙人的演講,主要是深入研究 ruby 記憶體沒有被釋放的問題,如何在 ruby 中調查這類的問題。我們可以使用 Shopify 提供的<a href="https://github.com/Shopify/ruby_memcheck" target="_blank" rel="noopener">套件</a>來做記憶體檢查。<br>講者提供的 <a href="https://blog.peterzhu.ca/assets/rubykaigi_2024_slides.pdf" target="_blank" rel="noopener">簡報</a>。</p><h3 id="Embedding-it-into-Ruby-code"><a href="#Embedding-it-into-Ruby-code" class="headerlink" title="Embedding it into Ruby code"></a>Embedding it into Ruby code</h3><p>主要在講將 ruby type 的裝飾。</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Uploaded my slides for <a href="https://twitter.com/hashtag/RubyKaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#RubyKaigi</a> 2024. <a href="https://t.co/nx4YSJKsAG" target="_blank" rel="noopener">https://t.co/nx4YSJKsAG</a></p>— Soutaro Matsumoto (@soutaro) <a href="https://twitter.com/soutaro/status/1791108891243344091?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Reducing-Implicit-Allocations-During-Method-Calling"><a href="#Reducing-Implicit-Allocations-During-Method-Calling" class="headerlink" title="Reducing Implicit Allocations During Method Calling"></a>Reducing Implicit Allocations During Method Calling</h3><p>Ruby 3.3 和 3.4 star operator 的差別,透過要記憶體位址的邏輯的調整讓 ruby 更有效率。</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Slides from my RubyKaigi 2024 talk "Reducing Implicit Allocations During Method Calling" are now available at <a href="https://t.co/mWDHHmB28h" target="_blank" rel="noopener">https://t.co/mWDHHmB28h</a></p>— Jeremy Evans (@jeremyevans0) <a href="https://twitter.com/jeremyevans0/status/1790983698818761083?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="RuboCop-LSP-and-Prism"><a href="#RuboCop-LSP-and-Prism" class="headerlink" title="RuboCop: LSP and Prism"></a>RuboCop: LSP and Prism</h3><p>Rubocop 引入內建的 language server,語言伺服器協定 Language Server Protocol (LSP) 為各種程式語言提供跨編輯器支援的現代標準。其中有提到目前兩大 ruby parser Lrama(LR) 和 Prism(Handcraft),rubocop 關於 Prism parser 的支援。這場人超多多到滿出來。<br>作者的<a href="https://koic.hatenablog.com/entry/rubocop-lsp-and-prism" target="_blank" rel="noopener">文章</a></p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">I've published "RuboCop: LSP and Prism" slides. Thank you! <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://twitter.com/hashtag/rubykaigiB?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigiB</a><a href="https://t.co/uawerJEgiX" target="_blank" rel="noopener">https://t.co/uawerJEgiX</a></p>— Koichi ITO (@koic) <a href="https://twitter.com/koic/status/1791007928096637206?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="It’s-about-time-to-pack-Ruby-and-Ruby-scripts-in-one-binary"><a href="#It’s-about-time-to-pack-Ruby-and-Ruby-scripts-in-one-binary" class="headerlink" title="It’s about time to pack Ruby and Ruby scripts in one binary"></a>It’s about time to pack Ruby and Ruby scripts in one binary</h3><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ギリギリ当日中に資料アップロードした!<a href="https://t.co/hzoCmBLkLD" target="_blank" rel="noopener">https://t.co/hzoCmBLkLD</a><a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a></p>— ahogappa (@ahogapParty) <a href="https://twitter.com/ahogapParty/status/1791104212790825357?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Adding-Security-to-Microcontroller-Ruby"><a href="#Adding-Security-to-Microcontroller-Ruby" class="headerlink" title="Adding Security to Microcontroller Ruby"></a>Adding Security to Microcontroller Ruby</h3><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">スライドこちらです / Slides are here <a href="https://t.co/JNmB5euUs9" target="_blank" rel="noopener">https://t.co/JNmB5euUs9</a> <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <a href="https://twitter.com/hashtag/rubykaigiC?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigiC</a></p>— sylph01 (@s01) <a href="https://twitter.com/s01/status/1791010806039240858?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Ruby-Committers-and-the-World"><a href="#Ruby-Committers-and-the-World" class="headerlink" title="Ruby Committers and the World"></a>Ruby Committers and the World</h3><p>這是 RubyKaigi 的慣例,每年會有 Ruby Committer 上台,台上台下一起討論 Ruby 未來相關的議題,很酷的體驗。</p><ul><li>Literal sting will be frozen in the future<br>印象中有提到為了這項遇到了一些挑戰,但還是會計劃在未來達成。<br>相關資料:<a href="https://gist.github.com/fxn/bf4eed2505c76f4fca03ab48c43adc72" target="_blank" rel="noopener">Ruby: The future of frozen string literals</a></li><li>Embedded: RBS: WDYT<br>有讓觀眾投票覺得哪種寫法比較好。</li><li>Replacing Ruby’s build system GNUAutotools → cmake</li><li>Do you want to remove the GVL?</li><li>Improving Ruby’s async usabilty e.g.async…awiat</li><li><code>defer</code> for Ruby?</li></ul><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">「・・・それは Matz のせいでしょ」の瞬間😊<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> <br><br>熱い議論いいね!👍 <a href="https://t.co/mvmaCSxGYG" target="_blank" rel="noopener">pic.twitter.com/mvmaCSxGYG</a></p>— emi sugita (@semiemi7) <a href="https://twitter.com/semiemi7/status/1791291891683926503?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 17, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="YJIT-Makes-Rails-1-7x-Faster"><a href="#YJIT-Makes-Rails-1-7x-Faster" class="headerlink" title="YJIT Makes Rails 1.7x Faster"></a>YJIT Makes Rails 1.7x Faster</h3><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Here's my deck for today's <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigi</a> talk: YJIT Makes Rails 1.7x faster <a href="https://t.co/Tq1s7wHWf6" target="_blank" rel="noopener">https://t.co/Tq1s7wHWf6</a></p>— k0kubun (@k0kubun) <a href="https://twitter.com/k0kubun/status/1791332281745080628?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 17, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Ruby-and-the-World-Record-Pi-Calculation"><a href="#Ruby-and-the-World-Record-Pi-Calculation" class="headerlink" title="Ruby and the World Record Pi Calculation"></a>Ruby and the World Record Pi Calculation</h3><blockquote class="twitter-tweet"><p lang="ja" dir="ltr">本日の発表資料です。来てくださったみなさま、ありがとうございました! Here's the deck from my RubyKaigi talk today. Thank you y'all for coming today!<a href="https://t.co/kA33vC2W2c" target="_blank" rel="noopener">https://t.co/kA33vC2W2c</a><a href="https://twitter.com/hashtag/rubykaigic?src=hash&ref_src=twsrc%5Etfw" target="_blank" rel="noopener">#rubykaigic</a></p>— Emma Haruka Iwao 🏳️🌈🏳️⚧️ (@Yuryu) <a href="https://twitter.com/Yuryu/status/1791390137915687158?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 17, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="Using-Ruby-in-the-browser-is-wonderful"><a href="#Using-Ruby-in-the-browser-is-wonderful" class="headerlink" title="Using Ruby in the browser is wonderful."></a>Using Ruby in the browser is wonderful.</h3><p>用很少的程式碼實現 Ruby in the browser,範例程式碼:<a href="https://github.com/ledsun/orbital_ring" target="_blank" rel="noopener">https://github.com/ledsun/orbital_ring</a> ,有趣的演講。</p><h3 id="Matz-Keynote"><a href="#Matz-Keynote" class="headerlink" title="Matz Keynote"></a>Matz Keynote</h3><p>這次 Matz 講了 4 次的 performance (大家看到都笑惹 XD),老實說我沒有百分百記得細節,之後可能要再看看影片惹。</p><ul><li>執行速度</li><li>提高記憶體效率</li><li>並行處理的改進</li><li>提高開發者體驗</li></ul><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>最後要感謝跟我一起去的 <a href="https://x.com/k_hno3" target="_blank" rel="noopener">Kasa</a> 小夥伴,日文比較好的她超厲害,可以跟大家好好的溝通。<br>也要謝謝 RubyKaigi 的所有工作人員,讓我們有很棒的體驗。</p><p>整體而言對我來說是很棒的經驗,體會到 Ruby 沒有死的感覺?大家都超熱情,雖然參加活動又聽演講,整個就是動腦又動心力,每天都蠻累的但是又很充實。很推薦大家都去體驗看看,議程聽不懂其實也沒關係,之後可以再針對有興趣的主題看影片複習,但參加活動認識朋友或是可以近身貼近各界大大,其實是很難得的事情,即便是身為 I 人的我,還是覺得蠻不錯的呢。</p>]]></content>
<tags>
<tag> RubyKaigi </tag>
</tags>
</entry>
<entry>
<title>WEBCONF 2023 Day 2 心得 & 紀錄</title>
<link href="/2023/08/20/WEBCONF-2023-Day-2/"/>
<url>/2023/08/20/WEBCONF-2023-Day-2/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,第一次參加 Webconf,每場演講都很精彩,所以要幫忙自己做一下紀錄,以免最會忘記的大腦忘光光,幫自己大腦外接一下 DB,以後還有這裡可以查看資料 XDDD</p><p>這次很多的主題都圍繞在這 10 年的變化,畢竟這次的 webconf 是時隔了 10 年的第二次 conference,是說我 10 年前還不會寫程式,這次參加感覺學到很多東西,感謝過去的自己讓我走到了這裡 (流淚),接下來紀錄一下我這次有聽的議程的小小心得,跟一些我自己的理解</p><blockquote><p>本來想一口氣寫完兩天的心得,想說只是想寫心得,比較有人性有點溫度的心得應該不用很久吧? 結果還是寫太久,加上因為忘記寫更久,所以還是分成兩篇好惹 QQ</p></blockquote><p>這篇是第二篇,想看第一篇的人可以點<a href="/2023/08/13/WEBCONF-2023-Day-1">這邊</a>。</p><h2 id="目錄-8-12"><a href="#目錄-8-12" class="headerlink" title="目錄 (8/12)"></a>目錄 (8/12)</h2><ol><li><a href="#APM-的守破離-Norman">APM 的守破離 - Norman</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FQoWpXB7yRc-ImH1yX_2C7Q" target="_blank" rel="noopener">共筆</a></li><li><a href="https://drive.google.com/file/d/1DBIV6tObjXPWin5NP1DDX8JUtiMF2uJg/view" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#鳳‧極意?!-Paul-Li">鳳‧極意?! - Paul Li</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FSVsZ1haXR-2bUhfcf_OYnA" target="_blank" rel="noopener">共筆</a></li><li><a href="https://blog.lalacube.com/mei/Reveal_phoenix_the_gokui.php" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策-Caesar-Chi">為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策 - Caesar Chi</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FdTk44rdmRaGJOFVN5oB4pg" target="_blank" rel="noopener">共筆</a></li><li><a href="https://speakerdeck.com/clonn/wei-he-ji-shu-lao-ren-zhe-yang-xiang-na-yang-zuo" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#講事實沒用,顧客才不聽這個!-Akane-Lee">講事實沒用,顧客才不聽這個! - Akane Lee</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2Fo2qdy0YySReFsx6Uf5pdDQ" target="_blank" rel="noopener">共筆</a></li></ul></li><li><a href="#10-年回顧:打造愛料理開發及營運團隊-Richard-Lee">10 年回顧:打造愛料理開發及營運團隊 - Richard Lee</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FVqSM6iJiRh6KqyIDTIeBiA" target="_blank" rel="noopener">共筆</a></li><li><a href="https://drive.google.com/file/d/1kNPtdVPrCXoJ2Nok62xZA6ZhvVoZFDHE/view" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#從-2013-到-2023-Web-Security-十年之進化及趨勢-Orange-Tsai">從 2013 到 2023: Web Security 十年之進化及趨勢! - Orange Tsai</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FCyz5StjtTyibrKIf9b7RCA" target="_blank" rel="noopener">共筆</a></li><li><a href="https://github.com/orangetw/My-Presentation-Slides/blob/main/data/2023-WEBCONF-from-2013-to-2023-the-Evolution-of-Web-Security.pdf" target="_blank" rel="noopener">簡報</a></li><li><a href="https://blog.orange.tw/2023/08/2023-webconf-the-evolution-of-web-security.html" target="_blank" rel="noopener">文章</a></li></ul></li><li><a href="#選擇適合你的技能組合-蒼時弦也">選擇適合你的技能組合 - 蒼時弦也</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FTNzR66T8QX6WxEdWuXQb4w" target="_blank" rel="noopener">共筆</a></li><li><a href="https://speakerdeck.com/elct9620/2023-webconf-xuan-ze-shi-he-ni-de-ji-neng-zu-he" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#Beyond-Technology-技術之外-從個人身心安頓到人類福祉追求-蔡明哲">Beyond Technology 技術之外 - 從個人身心安頓到人類福祉追求 - 蔡明哲</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2Fpk7crlHPTQumx2NzjWTdBQ" target="_blank" rel="noopener">共筆</a></li><li><a href="https://drive.google.com/file/d/1ZhMwaQ-fxj4QZ5k8qxnhC43iBPftoaqk/view" target="_blank" rel="noopener">簡報</a></li></ul></li></ol><h2 id="APM-的守破離-Norman"><a href="#APM-的守破離-Norman" class="headerlink" title="APM 的守破離 - Norman"></a>APM 的守破離 - Norman</h2><p>早上的時候我還是遲到了,到教室的時候滿人超滿,所以我前面也沒聽到,講者主要介紹他們在使用 <a href="https://www.elastic.co/observability/application-performance-monitoring" target="_blank" rel="noopener">Elastic APM</a> 的一些經驗分享。首先要建立可以收集資料的環境,有數據才知道哪裡有問題,接著追蹤有問題的地方,但講者也有要我們注意「區域效能瓶頸不一定是商業上的瓶頸」、「要關注解決的問題是否給技術或商業帶來價值」,所以有時候工程師如我覺得有問題其實不一定有問題的概念。另外這次參加 Webconf 遇到的 Kasa 小夥伴有跟我分享講者在 1 年前類似主題的分享<a href="https://www.youtube.com/watch?v=xskXc_e5lcY" target="_blank" rel="noopener">影片</a>,也放在這裡分享給大家。</p><h2 id="鳳‧極意?!-Paul-Li"><a href="#鳳‧極意?!-Paul-Li" class="headerlink" title="鳳‧極意?! - Paul Li"></a>鳳‧極意?! - Paul Li</h2><p>這場演講從投影片就讓我在內心不停的驚呼講者前端的實力 XDD,身為一個在前端移動小物件都覺得煩的後端(為主?)工程師,真的相當佩服很會前端的大大們。這場演講主要在講前端 form 的各種原生標籤的使用技巧,盡可能的使用瀏覽器原生的 tag,盡可能避免預期外的錯誤,完美展示最簡單最困難的一場演講。我覺得這場演講除了技術面的 pickup 以外,像這樣對於原生探究的精神是可以用在其他地方的,像是使用某個套件會先去看套件的文件、source code,先想想有沒有比較簡單的方式,盡可能複雜的事情簡單化,是需要學習的,那些看起來越簡單其實越困難的概念呢。</p><h2 id="為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策-Caesar-Chi"><a href="#為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策-Caesar-Chi" class="headerlink" title="為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策 - Caesar Chi"></a>為何技術老人這樣想那樣做?從技術前輩的視角理解技術決策 - Caesar Chi</h2><p>這是一場偏軟實力的演講,講者句句名言? 像是「人生唯一不變的就是變」、「<font color="#f00">不是提案不好、是「你」不好</font>」、「如果發現了一個很棒的技術想要推廣,你該:思考如何達到目的,而不是<font color="#f00">我</font>如何達到目的」、「隨手清理程式債(垃圾),隨手做功德」。以後要跟大家說大家好,我是沒有人 XDDDD。</p><h2 id="講事實沒用,顧客才不聽這個!-Akane-Lee"><a href="#講事實沒用,顧客才不聽這個!-Akane-Lee" class="headerlink" title="講事實沒用,顧客才不聽這個! - Akane Lee"></a>講事實沒用,顧客才不聽這個! - Akane Lee</h2><p>這場演講比較偏心理學層面的知識,講者一開始就帶大家一起思考,首先是「每個人的信念就一定是事實嗎? 」、「堅持自己與事實不符的信念那是說謊嗎? 」、「事實跟我所相信的事物,一點關係都沒有」,這讓我想到有些人會覺得自己講的就是事實,但真的是那樣嗎? 還是他講的是自己的信念呢? 大家都會有不同的認知。其中我覺得最有趣的地方在於我們每個人對於每件事情都會有一個接受區和拒絕區,來自於自己的信念,而在溝通的時候,兩個人的區域範圍重疊的地方,才是可以溝通的區間,也就是兩個極端想法的人其實是不能溝通的 XDDDDD,因為人都會有排斥心理,這很有趣,可以延伸很多,像是我們不可能讓世界上所有人喜歡,沒有一個世界上所有人都認同的方案,因為大家都會有不同的區間範圍,我在想這個樣子也就是為什麼有些公司會想找價值觀一樣的員工惹,因為這樣才能溝通 XD。另一個提到我覺得印象深刻的像是人會為自己找理由,我們喜歡做事情有個「理由」,講者舉例想要插隊列印東西的人,講了理由讓他先印的人比例就比較高,甚至講了一個像是廢話的理由比例更高。這場演講的結論是我們賣東西,賣的其實不是東西,是信仰。</p><h2 id="10-年回顧:打造愛料理開發及營運團隊-Richard-Lee"><a href="#10-年回顧:打造愛料理開發及營運團隊-Richard-Lee" class="headerlink" title="10 年回顧:打造愛料理開發及營運團隊 - Richard Lee"></a>10 年回顧:打造愛料理開發及營運團隊 - Richard Lee</h2><p>講者分享這 10 年打造產品的經驗,主要對於 10 年前提出的想法做一個覆盤,有覺得好的,也有覺得不好的,好的像是盡早部署、時常驗證,不好的像是過程中迷信新東西可以解決問題,讓架構變得太過複雜。結論是「時時要去思考,到底是在解決問題還是創造問題。」,是很棒的經驗分享。</p><h2 id="從-2013-到-2023-Web-Security-十年之進化及趨勢-Orange-Tsai"><a href="#從-2013-到-2023-Web-Security-十年之進化及趨勢-Orange-Tsai" class="headerlink" title="從 2013 到 2023: Web Security 十年之進化及趨勢! - Orange Tsai"></a>從 2013 到 2023: Web Security 十年之進化及趨勢! - Orange Tsai</h2><p>這場是我現在待的資安公司 <a href="https://devco.re" target="_blank" rel="noopener">DEVCORE</a> 的橘子大神演講,超多人來聽,還好有搶到位子? 講者分享了這 10 年來 Web Security 的進化,首先舉了一個 nginx 寫錯設定檔導致可以被攻擊的案例,「回報給 nginx 但他們覺得不算問題,因為是開發者寫錯 config」,所以各位開發者們不要再說資安太難拉 QQ (雖然我也覺得很難,咦?),不過覺得太難的也可以直接交給專業的來拉,突然變成業配文? 接著講者舉了 4 + 1 趨勢的例子如下:</p><ol><li>架構<ul><li>Hop-by-Hop Attack (RFC 標準要好好讀)<blockquote><p>覺得開發者如果開發到跟 http connection 相關的功能的時候要注意,如果有什麼像是為了方便開放 127.0.0.1 之類的情況也要特別注意漏洞的可能性。</p></blockquote></li><li>Web Cache Deception (什麼時候該 Cache 什麼時候不該 Cache?)<blockquote><p>開發者的思路應該會是,有權限問題的不應該 Cache 到所有人都可以得到。跟權限有關的東西都應該特別注意。</p></blockquote></li></ul></li><li>底層<ul><li>PHP 的 file_exists function<blockquote><p>檔案相關的處理是不是安全的,如果使用者可以傳入 path,可能就要注意惹。</p></blockquote></li><li>Prototype Pollution (因應 JavaScript 特性發展出的攻擊面)<blockquote><p>除了關切使用者輸入的安全性外,對於底層出現的漏洞,覺得有一個開發者需要關注的部分,就是版本更新,不要說一直都用很舊的版本都不更新,這樣就算語言或套件本身修正了安全性問題,專案還是不安全。另外就是在開發的時候要盡可能避免重要資料被外部修改的可能性。</p></blockquote></li></ul></li><li>不一致<ul><li>URL Parser<blockquote><p>網址是大家可以進入我們網站的入口,也會是駭客可以著手的地方,大門的保護也是很重要的,一個不小心就讓駭客走去你家廚房惹 QQ。</p></blockquote></li><li>JSON Parser<blockquote><p>因為對於輸入欄位的解析不一致而造成漏洞。感覺這類型的洞如果要根治的話似乎標準做法的統一也是一件事情,但好像很難的樣子嗎? 有種一個什麼各自解讀的感覺?</p></blockquote></li></ul></li><li>跨應用<ul><li>用公開 email 註冊私人 space<blockquote><p>注意公開資訊的權限設定。</p></blockquote></li><li>HTTPoxy Attack (你的規範不只是你的規範)<blockquote><p>發現 Ruby 有相對較早發現問題,表現不錯 XD</p></blockquote></li><li>防毒軟體的利用<blockquote><p>我們寫程式會用組合技巧,駭客也會用組合技巧 (不同意義) 攻擊啊 QQ</p></blockquote></li></ul></li><li>前端<blockquote><p>覺得所有互動的地方都可能是危險的入口 QQ</p></blockquote></li></ol><p>最後感覺很多開發者想躺平惹,但大家我們還有一個選項,就是交給專業的來 XDDDDD 資安問題就跟保險想要保障的東西很像,沒發生事情的時候沒感覺,發生事情的時候可能就嚴重惹。我們可以持續學習 + 交給專業的來。</p><h2 id="選擇適合你的技能組合-蒼時弦也"><a href="#選擇適合你的技能組合-蒼時弦也" class="headerlink" title="選擇適合你的技能組合 - 蒼時弦也"></a>選擇適合你的技能組合 - 蒼時弦也</h2><p>因為下午茶時間在攤位做測驗,這場晚惹一點點進去,前面有一點點沒聽到 QQ。講者先講了程式語言的歷史,原來 Ruby 的年紀比 C# 還老啊,以為有 C 就很老 (<del>Cindy:咦?</del>),接者講了開發框架的歷史,Rails 很老誒 XD「從時間角度看,不同時期推出的語言、框架剛好對應當時面對的問題」,不同語言有不同的特性,例如型別、struct、interface、物件、Mixin、Pointer、框架。</p><h3 id="解決方案-生態系決定選擇"><a href="#解決方案-生態系決定選擇" class="headerlink" title="解決方案 (生態系決定選擇)"></a>解決方案 (生態系決定選擇)</h3><ul><li>AL/ML: Python</li><li>快速製作產品原型: Ruby、PHP</li><li>網路服務: Golang</li><li>安全、系統底層: Rust</li><li>遊戲: C#(Unity)、 C++、Ruby(日本的遊戲伺服器公司會用)</li></ul><p>除了 pickup 技術特徵的選擇外,考量當下環境也是很重要的,不知道為啥突然想到機會成本 XD,如果有相當明確的目標可以選擇往目標的方向前進,但目前覺得如果不知道怎麼選的時候可以考慮選擇成本較低的方案,因為成本低隨時想要轉向都容易,隨時調整也很 ok (吧),不負責任心得。</p><h2 id="Beyond-Technology-技術之外-從個人身心安頓到人類福祉追求-蔡明哲"><a href="#Beyond-Technology-技術之外-從個人身心安頓到人類福祉追求-蔡明哲" class="headerlink" title="Beyond Technology 技術之外 - 從個人身心安頓到人類福祉追求 - 蔡明哲"></a>Beyond Technology 技術之外 - 從個人身心安頓到人類福祉追求 - 蔡明哲</h2><p>「今天的第一課:對過去抱著感恩」講者自稱是阿北,主要是阿北人生歷程分享,一開始講對過去懷抱感恩,感謝一路上互助合作的同事跟夥伴,所以有現在的自己。我想大家何嘗不是如此,我覺得除了感謝大家,也要感謝自己,謝謝一路走到現在的自己。講者在說人的一輩子有兩個日子跟你有關的時候,我還以為他要說出生和死亡 XDDD 老實說我自己是沒有一定要找到人生意義的那種想法就是惹。「安頓自己是一輩子的功課,學習面對當下」,講者提到覺察內在焦慮,但不排斥它,我想也是一些心理學的書提到的,面對自己內心的情緒,接受他,他是我們的一部分。老實說這場演講真的超棒的,感覺很有溫度、很溫暖的一場演講,還記得小時候喜歡看的卡通玩偶遊戲有一句「我們逃走吧」的台詞,當時我覺得那台詞很棒,每次想逃走的時候就想一下,然後暫時逃走,整理好自己又可以繼續小步前進,總結要「過去感恩、現在安頓、未來設定目標,有目標才不會茫然。」</p><h3 id="7-個職涯建議"><a href="#7-個職涯建議" class="headerlink" title="7 個職涯建議"></a>7 個職涯建議</h3><p>建議 1 – 向內看: 以自己為目標,不要把贏過所有人當作目標或壓力<br>建議 2 - 向外看: 對企業(行業)的熟悉度/適應力是重點<br>建議 3 – 不只人為中心,更要以人類福祉為中心<br>建議 4 - 不定著於科技,學習看得更廣<br>建議 5 - 不慌不忙,細細品嚐勝過囫圇吞棗<br>建議 6 - 面對問題,解決問題,不要等待他人<br>建議 7 - 不要死守你的角色與職稱</p>]]></content>
<tags>
<tag> webconf </tag>
</tags>
</entry>
<entry>
<title>WEBCONF 2023 Day 1 心得 & 紀錄</title>
<link href="/2023/08/13/WEBCONF-2023-Day-1/"/>
<url>/2023/08/13/WEBCONF-2023-Day-1/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,第一次參加 Webconf,每場演講都很精彩,所以要幫忙自己做一下紀錄,以免最會忘記的大腦忘光光,幫自己大腦外接一下 DB,以後還有這裡可以查看資料 XDDD</p><p>這次很多的主題都圍繞在這 10 年的變化,畢竟這次的 webconf 是時隔了 10 年的第二次 conference,是說我 10 年前還不會寫程式,這次參加感覺學到很多東西,感謝過去的自己讓我走到了這裡 (流淚),接下來紀錄一下我這次有聽的議程的小小心得,跟一些我自己的理解</p><blockquote><p>本來想一口氣寫完兩天的心得,想說只是想寫心得,比較有人性有點溫度的心得應該不用很久吧? 結果還是寫太久,加上因為忘記寫更久,所以還是分成兩篇好惹 QQ</p></blockquote><h2 id="目錄-8-11"><a href="#目錄-8-11" class="headerlink" title="目錄 (8/11)"></a>目錄 (8/11)</h2><ol><li><a href="#活用-GitHub-Copilot-開發-Web-應用程式-Will-保哥">活用 GitHub Copilot 開發 Web 應用程式 - Will 保哥</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FT5tQ48y8S5GQkAUx9qlgng" target="_blank" rel="noopener">共筆</a></li><li><a href="https://drive.google.com/file/d/1W7KZ2vwsZyyIC_iMdCxgadXWodWkrU6u/view?fbclid=IwAR1xCb_0lrIJOzTVk8ErcldtFoEfapYjfSL8UbktHT4PzAndFQjbVthr1vY" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#AI-驅動下的開發者體驗-Ruddy-老師">AI 驅動下的開發者體驗 - Ruddy 老師</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FTz4XDh74SqGDDZiGcWBaKg" target="_blank" rel="noopener">共筆</a></li><li><a href="https://onedrive.live.com/view.aspx?resid=68B24674607C69D9!349590&cid=68b24674607c69d9&authkey=!APB1fAuO9FcMusg&CT=1691922176301&OR=ItemsView" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#Live-Unified-language-and-almost-everything-important-蘇泰安">Live, Unified language, and (almost) everything important - 蘇泰安</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2Fk7hRoTQOSE2npLcZauRW2g" target="_blank" rel="noopener">共筆</a></li><li><a href="https://1drv.ms/b/s!AshqLgDLWwFYhMp3CQwv9A1igAZeJg?e=7f5OTw" target="_blank" rel="noopener">簡報</a></li><li><a href="https://github.com/taiansu/webconf_phx" target="_blank" rel="noopener">示範 repo</a></li></ul></li><li><a href="#成為前端建築師吧!透過-Frontend-Infra-為前端應用打造穩健且高效率的開發體驗-莫力全-Kyle-Mo">成為前端建築師吧!透過 Frontend Infra 為前端應用打造穩健且高效率的開發體驗 - 莫力全 (Kyle Mo)</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FcPQikssETpSBS33EgKkQKw" target="_blank" rel="noopener">共筆</a></li><li><a href="https://slides.com/oldmo860617/minimal" target="_blank" rel="noopener">簡報</a></li><li><a href="https://reurl.cc/Eodnj1" target="_blank" rel="noopener">文章</a></li></ul></li><li><a href="#如何在有限資源下實現十年的後端服務演進-Kewang">如何在有限資源下實現十年的後端服務演進 - Kewang</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FAB33rrJHSDWkRWGNkyF82Q" target="_blank" rel="noopener">共筆</a></li><li><a href="https://speakerdeck.com/kewang/ru-he-zai-you-xian-zi-yuan-xia-shi-xian-shi-nian-de-hou-duan-fu-wu-yan-jin" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣-游舒帆">跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣 - 游舒帆</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FmRsVX_zeSia-A19Zda2C3w" target="_blank" rel="noopener">共筆</a></li><li><a href="https://docs.google.com/presentation/d/1MkyIpBbV9QpGgn7DWd_R_pyA9InE63-k/edit#slide=id.p1" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#HackMD-的前世與今生,以及未來-Max-Wu">HackMD 的前世與今生,以及未來 - Max Wu</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2FW5l9SjAVSsSy1Z7zHxPlWA" target="_blank" rel="noopener">共筆</a></li><li><a href="https://hackmd.io/@MaxWu/hackmd-webconf-2023" target="_blank" rel="noopener">簡報</a></li></ul></li><li><a href="#從專業到商業:十年軟體架構變遷-Ant">從專業到商業:十年軟體架構變遷 - Ant</a><ul><li><a href="https://hackmd.io/@webconf/BkImQ0Ds3/%2F00roPb7NQKOEHNoFGT6hig" target="_blank" rel="noopener">共筆</a></li></ul></li></ol><h2 id="活用-GitHub-Copilot-開發-Web-應用程式-Will-保哥"><a href="#活用-GitHub-Copilot-開發-Web-應用程式-Will-保哥" class="headerlink" title="活用 GitHub Copilot 開發 Web 應用程式 - Will 保哥"></a>活用 GitHub Copilot 開發 Web 應用程式 - Will 保哥</h2><p>早上還是有點小小遲到,所以前面幾分鐘沒聽到,這場主要在說明 GitHub Copilot 的應用,可以透過註解或對話的方式讓 AI 幫忙寫程式碼,整體感覺要是熟悉的程式語言會比較能駕馭,因為產生的程式碼不一定是正確的,有相對的了解並且知道怎麼修改應該會是關鍵,另外也要注意版權和隱私的問題,在這場演講才知道原來正體中文跟繁體中文是有差別的,以後記得要叫 AI 產生正體中文惹。</p><h2 id="AI-驅動下的開發者體驗-Ruddy-老師"><a href="#AI-驅動下的開發者體驗-Ruddy-老師" class="headerlink" title="AI 驅動下的開發者體驗 - Ruddy 老師"></a>AI 驅動下的開發者體驗 - Ruddy 老師</h2><p>這次 Ruddy 老師提到的開發者體驗,我以前有看過老師的文章所以特別有印象,我還整理到自己的 <a href="https://hackmd.io/@cindyliu923/SyRJUOV-o" target="_blank" rel="noopener">筆記</a> 裡,整場下來超多重點可以畫:</p><ul><li>看見全貌最好的方法就是提問,老師是用寄信給自己的方法,我的話目前是用 slack 可以跟自己對話的方式,推薦給大家,這邊真的要超級提醒自己,有時候會不小心太注意細節而沒看到全貌,適時的往後站也是很重要的。<blockquote><p>這裡讓我想到曾經聽過 <a href="https://open.spotify.com/episode/654hHk4l2tXOaokpy7vRUp?si=Z3OGHs9cR12bCTAXH294JQ&utm_source=native-share-menu&fbclid=IwAR3urOEUFMe8vPXNU8bLYhnFGLsFTVFYs0HNS8evZ6WFKat6CT2hJF0U4-Y&nd=1" target="_blank" rel="noopener">科技菜鳥</a> 有一集 Mosky 講過的話:「當你沒有接受到完整事實的時候,無論做多麼精細的分析,都會是錯的」、「片面的事實就足以讓你做出錯誤的判斷」</p></blockquote></li><li>Good Enough 效應,只有擁有非常好的品質才能夠幸免於難,而「剛剛好的品質」者,勢必將沈淪,而如何追求卓越,答案是質疑 AI 所產出的答案,才能達到 Excellent,這邊感覺跟第一場呼應。</li><li>艾賓豪斯遺忘曲線,記得以前生物老師說過大腦最會做的事情就是遺忘,Ruddy 老師強調要把學習到的知識放在未來將要行徑的路上,最好把目前就會用到的撿起來用,知識吸收再多,沒有化成具體行為、發生改變的話,這叫做囤積知識,沒用。</li><li>簡單原則,我先做哪一件事之後,其他事就會變得比較容易,或者不必做了?這裡讓我想到以前發生過的一件事情,記得那時候我們開發團隊要開發一個相當複雜的計算功能 (細節已忘記),為了達成某個計算開發變得相當困難,但後來技術主管要我們跟 PM 討論我們想要達成的方便計算是不是其實可以不需要,如果根本不需要這個功能,也就不用花相當多的時間去做了。盡可能簡化,也是相當重要的,畢竟小步快速前進,比大步慢速前進更容易,跟織圍巾一樣?</li><li>面對 AI;我們應該以學習為中心,而非以獲取知識為中心</li><li>神燈不可能擁有全世界的知識,因為創新不斷地在發生</li></ul><h2 id="Live-Unified-language-and-almost-everything-important-蘇泰安"><a href="#Live-Unified-language-and-almost-everything-important-蘇泰安" class="headerlink" title="Live, Unified language, and (almost) everything important - 蘇泰安"></a>Live, Unified language, and (almost) everything important - 蘇泰安</h2><p>主要在說 LiveView on Phoenix、LiveWire on Laravel、HotWire on Rails 三種的比較。其他印象比較深刻的地方是一般程式語言不太能直接地看到物件,但 <a href="https://elixir-lang.org/" target="_blank" rel="noopener">elixir</a> 可以透過 <a href="https://elixir-lang.org/getting-started/debugging.html#observer" target="_blank" rel="noopener">observer</a> 進行各種觀察,蠻酷的。</p><h2 id="成為前端建築師吧!透過-Frontend-Infra-為前端應用打造穩健且高效率的開發體驗-莫力全-Kyle-Mo"><a href="#成為前端建築師吧!透過-Frontend-Infra-為前端應用打造穩健且高效率的開發體驗-莫力全-Kyle-Mo" class="headerlink" title="成為前端建築師吧!透過 Frontend Infra 為前端應用打造穩健且高效率的開發體驗 - 莫力全 (Kyle Mo)"></a>成為前端建築師吧!透過 Frontend Infra 為前端應用打造穩健且高效率的開發體驗 - 莫力全 (Kyle Mo)</h2><p>主要在講前端 Infra 的應用,起源於多種前端專案違反了 DRY (Don’t Repeat Yourself) 原則的問題,所以需要將一些事情自動化,或者進行封裝,分享了以下幾種工具或做法:</p><ul><li><a href="https://github.com/GoogleChrome/lighthouse-ci" target="_blank" rel="noopener">Lighthouse CI</a></li><li><a href="https://www.sonarsource.com/products/sonarqube/" target="_blank" rel="noopener">SonarQube</a></li><li>Custom ESLint Config</li><li>Project Generator For new project</li><li>Monitoring(<a href="https://grafana.com/docs/grafana/latest/dashboards/" target="_blank" rel="noopener">Grafana Dashboard</a>)</li><li>DAST For Comply With DevSecOps</li></ul><p>老實說我覺得不單只是前端,後端都是一樣的,需要考慮各種可以自動化及增加穩定度的方式,另外這場聽到講者提到理解需求,不需過度導入 [Dont’ over engineering],也是最近一直提醒自己的事情。</p><h2 id="如何在有限資源下實現十年的後端服務演進-Kewang"><a href="#如何在有限資源下實現十年的後端服務演進-Kewang" class="headerlink" title="如何在有限資源下實現十年的後端服務演進 - Kewang"></a>如何在有限資源下實現十年的後端服務演進 - Kewang</h2><p>這場演講主要講者分享 Funliday-旅遊規劃 這 10 年來的演進,包含技術 (ex: Redis - Sorted Set 實作 autocomplete) 與非技術 (ex: 分銷營運困難點),我還蠻喜歡這類型的分享,因為前人的經驗總是會有很多的參考價值,其實小型團隊因為人力資源有限的情況,總是會有一條龍? 如何在有限的資源下繼續前進,一定會有適當的取捨的。</p><h2 id="跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣-游舒帆"><a href="#跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣-游舒帆" class="headerlink" title="跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣 - 游舒帆"></a>跳脫技術職與管理職的二分選擇,技術管理職讓職涯無限寬廣 - 游舒帆</h2><p>講者提到技術跟管理為什麼要分開?讓我們去反思,有些時候問題不一定是要二選一,也許是可以直接 merge? 「技術的價值,在於創造價值」,這邊超有感,這個價值是什麼,其實也是跟商業面、使用面息息相關的,如果只有技術是沒有用的,重點在於使用了技術解決了什麼樣的問題。「拒絕參與政治的結果,就是被糟糕的人統治。」像是開發工程師只是說要做什麼樣的東西,還是說要思考做的東西有沒有解決問題,有沒有積極參與理解需求,也是一件相當重要的事情,是時常需要提醒自己的事情,有時候還是會忘記 QQ。我們究竟是被動的接受還是積極的參與,也許會是不同的結局。後面講者展示了打造高效開發團隊的圖,讓我們理解開發不單單只是技術開發的事情,應該可以嘗試用更全面的角度去看事情,技術是重要的,但不是只有技術重要,嘗試從 high level 的角度去看,也許會更知道要做什麼會更好,與各位開發者共勉之。</p><h2 id="HackMD-的前世與今生,以及未來-Max-Wu"><a href="#HackMD-的前世與今生,以及未來-Max-Wu" class="headerlink" title="HackMD 的前世與今生,以及未來 - Max Wu"></a>HackMD 的前世與今生,以及未來 - Max Wu</h2><p>講者花了一些時間推坑 <a href="https://socket.io/" target="_blank" rel="noopener">socket.io</a>? 後面講了一些 HackMD 以前有過的漏洞,主要是服務的本體會有相當多使用者互動的過程,這些過程想必也是漏洞的可能,所以講者提到了一些被發現的漏洞 (主要是 XSS) 以及修復的方法,另外有提到 <a href="https://help.imgur.com/hc/en-us/articles/14415587638029/?fbclid=IwAR3NPKm0j4EPEUBmrXv-ZNq19LIbbVym1gjmMdrrMsrWyF2PFlopNoy-trE" target="_blank" rel="noopener">Imgur</a> 圖片處理的方式。<br>提到的 XSS 相關文章 list 如下:</p><ul><li><a href="https://blog.orange.tw/2019/03/a-wormable-xss-on-hackmd.html" target="_blank" rel="noopener">Orange - A Wormable XSS on HackMD!</a></li><li><a href="https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md" target="_blank" rel="noopener">k1tten - HackMD Stored XSS & Bypass CSP with Google Tag Manager</a></li><li><a href="https://blog.maple3142.net/2023/06/21/hackmd-xss-again/" target="_blank" rel="noopener">maple3142 - HackMD XSS, Again</a></li></ul><h2 id="從專業到商業:十年軟體架構變遷-Ant"><a href="#從專業到商業:十年軟體架構變遷-Ant" class="headerlink" title="從專業到商業:十年軟體架構變遷 - Ant"></a>從專業到商業:十年軟體架構變遷 - Ant</h2><p>講者提到專業跟商業之間的不解和衝突,這大概需要拉個線讓兩端可以稍微聽聽看另一方的想法。「假如你是個面試官,現在的你,跟十年後的你,你會選用哪一個人?」讓自己去反思一下 (<del>雖然我比較希望 10 年後可以一半退休拉?</del>) 而這 10 年間架構的變化,從沒有規範、沒有規格演進到 Dev(Sec)Ops = DevOps / SecOps,因為不同的歷史而產生不同的方式,我想未來也會有更多新的方式,身為工程師的我們不停地學會新的方式也是很重要的。講者提到系統架構不單是系統架構的事情,也關乎公司的組織架構,我想如何在適合的當下找出適合的架構,然後可以適應變化並且可以持續演進也是很重要的,這大概也是為什麼開發會強調可讀性,因為好讀才會好變化,至少在改之前要知道本來在做啥的概念,「架構即演化,預想但不過早最佳化」。歷史不斷重演,這個讓我想到流行的東西好像也是會不段重演,麥當勞優惠或限定的東西也是? 結論是組織持續獲利與成長的權衡是 UX (User Experience) + DX (Developer Experience),我自己的理解是使用者很開心,開發者也很開心 XD 實踐為 SLA + DORA + Observability (如附圖)。</p><p><img src="https://hackmd.io/_uploads/H1dKuuX32.png" alt=""></p>]]></content>
<tags>
<tag> webconf </tag>
</tags>
</entry>
<entry>
<title>Cross-Site Request Forgery (CSRF)</title>
<link href="/2022/11/20/CSRF/"/>
<url>/2022/11/20/CSRF/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,發現自己最近記憶力真的不好,以前看過的東西還是不記得,最近嚴重到 3 天前的事情也忘光,所以想盡可能做紀錄,於是這篇文章就誕生了QQ</p><p>又過了一陣子發現這文章沒寫完,而且還有點混亂 XDD,趁過年的時候做了個整理,這篇文章最大的目的是如果我又忘記了可以直接翻來看,如果我有寫錯也歡迎大家留言告訴我唷。</p><object width="480" height="385"> <param name="movie" value="https://www.youtube.com/v/m0EHlfTgGUU"></param> <param name="allowFullScreen" value="true"></param> <param name="allowscriptaccess" value="always"></param> <embed src="https://www.youtube.com/v/m0EHlfTgGUU" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object><h2 id="什麼是-Cross-Site"><a href="#什麼是-Cross-Site" class="headerlink" title="什麼是 Cross Site"></a>什麼是 Cross Site</h2><p>Cross Site 中文是跨網站,講完(誒!),如何被說成是跨網站,目前我覺得是如果在同源政策中被定義的非同源網站,在非同源網站之間做資源存取,應該就可以被稱為 cross site。</p><h2 id="Same-origin-policy"><a href="#Same-origin-policy" class="headerlink" title="Same-origin policy"></a><a href="https://zh.wikipedia.org/zh-tw/%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5" target="_blank" rel="noopener">Same-origin policy</a></h2><p>同源政策是指在 Web 瀏覽器中,允許某個網頁腳本訪問另一個網頁的數據,但前提是這兩個網頁必須有相同的 URI、主機名和埠號,一旦兩個網站滿足上述條件,這兩個網站就被認定為具有相同來源。即同源政策限制了程式碼和不同網域資源間的互動。</p><p>同源政策僅適用於指令碼,這意味著某網站可以通過相應的 HTML 標籤存取不同來源網站上的圖像、CSS 和動態載入指令碼等資源。而跨站請求偽造就是利用同源政策不適用於 HTML 標籤的缺陷。</p><blockquote><p>推薦大家看這個 <a href="https://www.youtube.com/watch?v=bSJm8-zJTzQ" target="_blank" rel="noopener">youtube</a> 講了一下關於 The Same Origin Policy 的歷史,從 Windows 95 出發 XDDD。</p></blockquote><h2 id="什麼是-Cross-Site-Request-Forgery-CSRF"><a href="#什麼是-Cross-Site-Request-Forgery-CSRF" class="headerlink" title="什麼是 Cross-Site Request Forgery (CSRF)"></a>什麼是 Cross-Site Request Forgery (CSRF)</h2><p>Cross-Site Request Forgery 簡稱 CSRF,中文叫做跨站請求偽造,也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種利用使用者在當前已登入的 Web 應用程式上執行非本意操作的攻擊方法。跟跨站腳本攻擊(XSS)相比,XSS 利用的是使用者對指定網站的信任,CSRF 利用的是網站對使用者網頁瀏覽器的信任。</p><blockquote><p><a href="https://www.youtube.com/watch?v=KaEj_qZgiKY" target="_blank" rel="noopener">CSRF Introduction and what is the Same-Origin Policy? - web 0x04</a> 這個影片做了一段簡單的說明和示範</p></blockquote><blockquote><p><a href="https://www.youtube.com/watch?v=5joX1skQtVE" target="_blank" rel="noopener">What is a CSRF? | OWASP Top 10 2013 | Video by Detectify</a> 這個影片示範了一次 CSRF 的攻擊</p></blockquote><h2 id="Rails-的-CSRF-對策"><a href="#Rails-的-CSRF-對策" class="headerlink" title="Rails 的 CSRF 對策"></a>Rails 的 CSRF 對策</h2><blockquote><p>most Rails applications use cookie-based sessions. Either they store the session ID in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie.</p></blockquote><blockquote><p>By default, Rails includes an unobtrusive scripting adapter, which adds a header called X-CSRF-Token with the security token on every non-GET Ajax call. Without this header, non-GET Ajax requests won’t be accepted by Rails. When using another library to make Ajax calls, it is necessary to add the security token as a default header for Ajax calls in your library. To get the token, have a look at <meta name='csrf-token' content='THE-TOKEN'> tag printed by <%= csrf_meta_tags %> in your application view.</p></blockquote><p>Rails 預設是 cookie-based sessions 的,關於 CSRF 的攻擊,Rails 也用一套解法 => CSRF token。<br>CSRF token 保護的重點在於,由於每次請求都會發送 cookie session_id(或帶有 JWT 的 cookie),因此我們需要一個額外的訊息(CSRF token),服務器將確認為用戶的瀏覽器操作,而不是惡意鏈接。</p><h2 id="為什麼-rails-api-mode-預設沒有處理-CSRF-token"><a href="#為什麼-rails-api-mode-預設沒有處理-CSRF-token" class="headerlink" title="為什麼 rails api mode 預設沒有處理 CSRF token"></a>為什麼 rails api mode 預設沒有處理 CSRF token</h2><blockquote><p>Rails 文件中提到: <a href="https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html" target="_blank" rel="noopener">APIs may want to disable this behavior since they are typically designed to be state-less: that is, the request API client handles the session instead of Rails.</a></p></blockquote><h2 id="前後端分離的-CSRF-token"><a href="#前後端分離的-CSRF-token" class="headerlink" title="前後端分離的 CSRF token"></a>前後端分離的 CSRF token</h2><p>當前後端完全分離的時候,由於前端不是 Rails 渲染的,我們就不能直接用 <code>csrf_meta_tags</code> <del>Rails 魔法</del>。</p><p><a href="https://blog.eq8.eu/article/rails-api-authentication-with-spa-csrf-tokens.html" target="_blank" rel="noopener">Rails CSRF protection for SPA</a> 這篇文章詳細說明了各種實作方式,其中一種方式 <strong>通過身份驗證標頭使用 JWT 令牌的 SPA</strong> 提供給我們另一個觀點,就是如果不用 cookie 作身份驗證其實就不會有 CSRF 的問題,但要注意的是僅僅沒有 CSRF 問題,並不意味著跨站腳本攻擊 (XSS) 無法竊取 token!當後端 API 只接受 header(不接受 cookie)時,前端 SPA 會面臨更大的安全壓力。</p><h2 id="參考資料"><a href="#參考資料" class="headerlink" title="參考資料"></a>參考資料</h2><ul><li><a href="https://5xruby.tw/posts/hello-spa-rails-api-server" target="_blank" rel="noopener">是誰在哈囉? 如何搞定 SPA 與 API Server 的登入驗證</a></li><li><a href="https://ihower.tw/rails/fullstack-security-csrf.html" target="_blank" rel="noopener">Rails 實戰聖經 - CSRF 跨站請求偽造</a></li><li><a href="https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef" target="_blank" rel="noopener">A Deep Dive into CSRF Protection in Rails</a></li><li><a href="https://tech-blog.cymetrics.io/posts/jo/zerobased-cross-site-request-forgery/" target="_blank" rel="noopener">零基礎資安系列(一)-認識 CSRF(Cross Site Request Forgery)</a></li><li><a href="https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf" target="_blank" rel="noopener">Cross-Site Request Forgery (CSRF)</a></li><li><a href="https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS" target="_blank" rel="noopener">跨來源資源共用(CORS)</a></li></ul>]]></content>
<tags>
<tag> security </tag>
<tag> CSRF </tag>
<tag> CSRF Token </tag>
</tags>
</entry>
<entry>
<title>幂等性 (Idempotent)</title>
<link href="/2022/07/06/Idempotent/"/>
<url>/2022/07/06/Idempotent/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,關於<strong>幂等性 (Idempotent)</strong>我已經聽説過 3 次了,還記不住(事不過三?亂用成語),所以決定記錄下來,順便分享給大家,下面會是我的筆記,如果有錯歡迎大家留言跟我說唷!</p><h2 id="什麼是冪等性?"><a href="#什麼是冪等性?" class="headerlink" title="什麼是冪等性?"></a>什麼是冪等性?</h2><p>冪等性最早是數學裡面的一個概念,後來被用於計算機領域,用於表示任意多次請求均與一次請求執行的結果相同,也就是說對於一個介面而言,無論呼叫了多少次,最終得到的結果都是一樣的。詳細可以參考 <a href="https://zh.wikipedia.org/zh-tw/%E5%86%AA%E7%AD%89" target="_blank" rel="noopener">維基百科</a>。</p><h2 id="HTTP-的冪等性"><a href="#HTTP-的冪等性" class="headerlink" title="HTTP 的冪等性"></a>HTTP 的冪等性</h2><ul><li>GET: 安全(Safe)且 冪等(Idempotent),用來讀取數據</li><li>POST: 不安全(Non-safe) 且不是冪等(non-idempotent),用來新增數據或執行某個操作</li><li>PUT: 不安全(Non-safe) 但冪等(idempotent),用來置換數據</li><li>PATCH: 不安全(Non-safe) 且不是冪等(non-idempotent),用來修改數據</li><li>DELETE: 不安全(Non-safe) 但冪等(idempotent),用來刪除數據</li><li>參考: <a href="https://ihower.tw/blog/archives/6483" target="_blank" rel="noopener">HTTP Verbs: 談 POST, PUT 和 PATCH 的應用</a>、<a href="https://ihower.tw/cs/networking-http.html#http-%E6%96%B9%E6%B3%95" target="_blank" rel="noopener">HTTP 方法</a></li></ul><p>從 RESTful 服務的角度來看,要使操作(或服務調用)具有冪等性,客戶端可以在產生相同結果的同時重複進行相同的調用。換句話說,發出多個相同的請求與發出單個請求具有相同的效果。請注意,雖然冪等操作在服務器上產生相同的結果(沒有副作用),但響應本身可能不同(例如,資源的狀態可能會在請求之間發生變化)。</p><p>PUT 和 DELETE 方法被定義為冪等的。但是,有一個關於 DELETE 的警告。DELETE 的問題,如果成功通常會返回 200(OK)或 204(No Content),在後續調用中通常會返回 404(Not Found),除非該服務被配置為“標記”要刪除的資源而不實際刪除它們。但是,當服務實際刪除資源時,下一次調用將找不到資源將其刪除並返回 404。但是,每次 DELETE 調用後服務器上的狀態都是一樣的,只是響應不同。</p><h3 id="為什麼-PATCH-是-non-idempotent"><a href="#為什麼-PATCH-是-non-idempotent" class="headerlink" title="為什麼 PATCH 是 non-idempotent"></a>為什麼 PATCH 是 non-idempotent</h3><blockquote><p>A PATCH is not necessarily idempotent, although it can be. Contrast this with PUT; which is always idempotent. The word “idempotent” means that any number of repeated, identical requests will leave the resource in the same state. For example if an auto-incrementing counter field is an integral part of the resource, then a PUT will naturally overwrite it (since it overwrites everything), but not necessarily so for PATCH.</p></blockquote><p>從 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" target="_blank" rel="noopener">MDN Web Docs</a> 的說明可以知道 PATCH 可以是 idempotent,但不一定是,像是自動增加數值的欄位更新。</p><h2 id="如何保證介面的冪等性?"><a href="#如何保證介面的冪等性?" class="headerlink" title="如何保證介面的冪等性?"></a>如何保證介面的冪等性?</h2><h3 id="前端攔截-但可能被跳過"><a href="#前端攔截-但可能被跳過" class="headerlink" title="前端攔截 (但可能被跳過)"></a>前端攔截 (但可能被跳過)</h3><p>disable 按鈕</p><p>前端這邊我沒有詳細察,所以只列了這個QQ</p><h3 id="使用資料庫實現冪等性"><a href="#使用資料庫實現冪等性" class="headerlink" title="使用資料庫實現冪等性"></a>使用資料庫實現冪等性</h3><ol><li>唯一索引,防止新增髒資料</li><li>悲觀鎖<br> db lock</li><li>樂觀鎖<br> 用 version 控制<br> 先 select 拿到版號,做完更新版號+1<br> 如果版號不是差 1 就不動做</li><li>Atomic Transactions<ul><li><a href="https://brandur.org/http-transactions#footnote-1-source" target="_blank" rel="noopener">Using Atomic Transactions to Power an Idempotent API</a></li><li><a href="https://apidock.com/rails/ActiveRecord/ConnectionAdapters/DatabaseStatements/transaction" target="_blank" rel="noopener">transaction(isolation: :serializable)</a><br>簡單說就是用 db 最嚴格的隔離等級來避免資料重複。<br>註:關於 isolation 可以參考我以前的 <a href="https://cindyliu923.com/2020/05/31/Database-Transaction/#SQL-%E6%A8%99%E6%BA%96%E4%B8%AD%E5%AE%9A%E7%BE%A9%E4%BA%86%E5%9B%9B%E7%A8%AE%E6%95%B8%E6%93%9A%E5%BA%AB%E7%9A%84%E9%9A%94%E9%9B%A2%E7%B4%9A%E5%88%A5">文章</a></li></ul></li><li>select insert<br>併發不高的後臺系統,或者一些任務 JOB,為了支援冪等,支援重複執行,簡單的處理方法是,先查詢下一些關鍵資料,判斷是否已經執行過,在進行業務處理,就可以了。<br>注意:核心高併發流程不要用這種方法。</li></ol><h3 id="使用分散式鎖實現冪等性"><a href="#使用分散式鎖實現冪等性" class="headerlink" title="使用分散式鎖實現冪等性"></a>使用分散式鎖實現冪等性</h3><p>如果是分佈式系統,構建全域性唯一索引比較困難,例如唯一性的欄位沒法確定,這時候可以引入分散式鎖,通過第三方的系統(<a href="https://redis.io/" target="_blank" rel="noopener">redis</a> 或 <a href="https://zookeeper.apache.org/" target="_blank" rel="noopener">zookeeper</a>),在業務系統插入資料或者更新資料,獲取分散式鎖,然後做操作,之後釋放鎖,這樣其實是把多執行緒併發的鎖的思路,引入多個系統,也就是分散式系統中的解決思路。</p><h3 id="token-機制"><a href="#token-機制" class="headerlink" title="token 機制"></a>token 機制</h3><p>參考 - <a href="https://cloud.tencent.com/developer/article/1839609#1.-token%E6%9C%BA%E5%88%B6" target="_blank" rel="noopener">流程圖</a><br>這裡要結合業務考慮這種場景:如果請求處理失敗,前端是否需要重新申請 token 進行重試(因為此時 token 在服務端已經被刪除)。<br>tip: 先刪除 token 再進行業務操作</p><h3 id="狀態機"><a href="#狀態機" class="headerlink" title="狀態機"></a>狀態機</h3><p>在設計單據相關的業務,或者是任務相關的業務,肯定會涉及到狀態機(狀態變更圖),就是業務單據上面有個狀態,狀態在不同的情況下會發生變更,一般情況下存在有限狀態機,這時候,如果狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變更,理論上是不能夠變更的,這樣的話,保證了<a href="https://zh.wikipedia.org/zh-tw/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA" target="_blank" rel="noopener">有限狀態機</a>的冪等。</p><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>幂等性其實就是想辦法確保最終一致性,可以當成一種概念,用在我們設計程式的時候,思考的一個方向,像是這個業務邏輯是否有需要確保最終一致性,是否會有重複執行的可能性,重複執行的時候要視為成功或失敗,如何避免重複執行造成的錯誤,都是我們要思考的地方。</p><hr><p>其他參考資料:</p><ul><li><a href="https://blog.aotoki.me/posts/2017/10/30/The-design-of-Stripe-s-Idempotency-Keys/#more" target="_blank" rel="noopener">Stripe 的 Idempotency Key 設計機制</a></li><li><a href="https://iter01.com/566612.html" target="_blank" rel="noopener">如何保證介面的冪等性?常見的實現方案有哪些?</a></li><li><a href="https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/614601/" target="_blank" rel="noopener">程式設計冪等設計:資料最終一致性的保證</a></li><li><a href="https://cloud.tencent.com/developer/article/1839609?from=article.detail.1605254" target="_blank" rel="noopener">一文理解如何實現接口的冪等性</a></li><li><a href="https://schweizerischebundesbahnen.github.io/api-principles/restful/best-practices/" target="_blank" rel="noopener">API Principles - Best Practices</a></li><li><a href="https://william-yeh.net/post/2020/03/idempotency-key-test/" target="_blank" rel="noopener">Idempotency Key:原理與實測</a></li><li><a href="https://docs.gitlab.com/ee/development/sidekiq/idempotent_jobs.html" target="_blank" rel="noopener">Sidekiq idempotent jobs</a></li><li><a href="https://ihower.tw/cs/web-apis.html" target="_blank" rel="noopener">Web APIs 設計</a></li><li><a href="https://www.restapitutorial.com/lessons/idempotency.html" target="_blank" rel="noopener">What Is Idempotence?</a></li></ul>]]></content>
<tags>
<tag> Idempotent </tag>
<tag> programming design </tag>
</tags>
</entry>
<entry>
<title>無瑕的程式碼番外篇 - 測試驅動開發 / 練習</title>
<link href="/2022/02/04/the-clean-coder-five-six/"/>
<url>/2022/02/04/the-clean-coder-five-six/</url>
<content type="html"><![CDATA[<p><img src="image.jpg" alt="image"><br>大家好,我是 Cindy,最近發現無暇程式碼的番外篇蠻好看的,適合在職場打滾的工程師們看,比起 clean code,<a href="https://www.books.com.tw/products/0010598217" target="_blank" rel="noopener">the clean coder</a> 比較算是軟實力的部分,主要是在說身為一個專業的工程師應該要有怎麼樣的態度、原則與行動。</p><p>今天這篇是這篇會是第五、六章節的重點及心得整理,想先看之前章節的人可以點選下面連結:</p><ul><li><a href="/2022/01/28/the-clean-coder-one">無瑕的程式碼番外篇 - 專業主義</a></li><li><a href="/2022/01/31/the-clean-coder-two-three">無瑕的程式碼番外篇 - 説「不」/ 説 「是」</a></li><li><a href="/2022/02/02/the-clean-coder-four">無瑕的程式碼番外篇 - 寫程式</a></li></ul><h2 id="測試驅動開發"><a href="#測試驅動開發" class="headerlink" title="測試驅動開發"></a>測試驅動開發</h2><p>在開始之前推薦大家看看這個影片 - <a href="https://www.youtube.com/watch?v=HFVjTFy42hI" target="_blank" rel="noopener">測試驅動開發 : 3 大法則 + 5 大好處 | 程式 x 開發 | 撰寫 單元測試 速度更快 【Gamma Ray 軟體工作室】</a>,我覺得蠻好看的,很清楚的說明什麼是 TDD,並教大家如何實作,看了這部影片之後我也發現,過去自己在做程式碼重構的時候,也是有用這樣的方式,主要重點在於<strong>先有測試</strong>,可以更快的測試我們所寫的程式碼,更快的修改就可以更快速的開發,形成一個良好的循環,用這樣的方式也可以盡量避免自己寫出高耦合的程式碼,越是方便獨立測試的程式碼越是高聚合、低耦合。</p><h3 id="畫重點"><a href="#畫重點" class="headerlink" title="畫重點"></a>畫重點</h3><ul><li>如果缺乏極高覆蓋率的自動化單元測試,如何能夠做到每次修改程式碼後都對程式碼進行測試?</li><li>TDD 三大法則<ul><li>在撰寫一個單元測試(測試失敗的單元測試)前,不可撰寫任何產品程式</li><li>只撰寫剛好無法通過的單元測試,不能編譯也算無法通過</li><li>只撰寫剛好能通過當前測試失敗的產品程式</li></ul></li><li>優勢<ul><li>勇氣<blockquote><p>讓我們有修改程式碼的勇氣,有了這樣的勇氣,才能相信程式碼可以越改越好。</p></blockquote></li><li>文件<ul><li>單元測試就是文件。它們描述了系統的最底層設計細節。</li></ul></li><li>設計<ul><li>基於測試先行的需要,會迫使你去思考什麼才是<strong>好的設計</strong>。</li></ul></li></ul></li><li>不使用 TDD 就說明你可能還不夠專業<blockquote><p>派(兇)</p></blockquote></li></ul><h2 id="練習"><a href="#練習" class="headerlink" title="練習"></a>練習</h2><p>專業人士都需要借助專門的訓練來提升自己的技能,無一例外。例如音樂家、運動員等等。</p><h3 id="畫重點-1"><a href="#畫重點-1" class="headerlink" title="畫重點"></a>畫重點</h3><ul><li>現在我們有更好的工具,更好的語言。可是,敘述的本質並沒有隨著時間而改變。20 世紀 60 年代的程式設計師完全可以看懂 2012 年的程式碼。<blockquote><p>這裡我想到,這也是我們為什麼要學習前人知識的原因。</p></blockquote></li><li>作者的練習<ul><li><a href="http://www.butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" target="_blank" rel="noopener">The Bowling Game Kata</a></li><li><a href="http://www.butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" target="_blank" rel="noopener">The Prime Factors Kata</a></li><li><a href="https://thecleancoder.blogspot.com/2010/10/craftsman-62-dark-path.html" target="_blank" rel="noopener">The Craftsman 62, The Dark Path.</a></li></ul></li><li>保持不落伍的方法是為開放原始碼貢獻程式碼,就像律師和醫生參加公益活動一樣。</li><li>如果你是 Java 程式設計師,請為 Rails 專案做點貢獻。如果你為老闆寫了很多 C++,可以找個 Python 專案貢獻程式碼。<blockquote><p>這邊我覺得作者的觀點蠻有趣的,用這樣的方式可以拓展自身的經驗。</p></blockquote></li><li>專業程式設計師用『自己的時間』來練習。</li><li>『練習』的時候你是賺不到錢的,但是練習之後,你會獲得回報,而且是豐厚的回報。</li></ul><blockquote><p>如果有在看這類文章的人,恭喜你踏出了一步,我們一起加油吧!嗚嗚</p></blockquote>]]></content>
<tags>
<tag> Clean Coder </tag>
</tags>
</entry>
<entry>
<title>無瑕的程式碼番外篇 - 寫程式</title>
<link href="/2022/02/02/the-clean-coder-four/"/>
<url>/2022/02/02/the-clean-coder-four/</url>
<content type="html"><![CDATA[<p><img src="image.jpg" alt="image"><br>大家好,我是 Cindy,最近發現無暇程式碼的番外篇蠻好看的,適合在職場打滾的工程師們看,比起 clean code,<a href="https://www.books.com.tw/products/0010598217" target="_blank" rel="noopener">the clean coder</a> 比較算是軟實力的部分,主要是在說身為一個專業的工程師應該要有怎麼樣的態度、原則與行動。</p><p>今天這篇是這篇會是第四章節的重點及心得整理,想先看之前章節的人可以點選下面連結:</p><ul><li><a href="/2022/01/28/the-clean-coder-one">無瑕的程式碼番外篇 - 專業主義</a></li><li><a href="/2022/01/31/the-clean-coder-two-three">無瑕的程式碼番外篇 - 説「不」/ 説 「是」</a></li></ul><p>作者從他練習打字的故事,帶出他發現要精熟掌握每項技藝,關鍵都是要具備『信心』和『出錯感知』。作者強調本章節提到的原則,是源自於作者本身在寫程式的心理、精神和情緒,這些會是『信心』和『出錯感知』的泉源。而原則不一定適用於所有人,是作者的經驗談。</p><h2 id="畫重點"><a href="#畫重點" class="headerlink" title="畫重點"></a>畫重點</h2><ul><li>聚精會神:<ul><li>關注點:<ol><li>程式碼能正常工作。</li><li>程式碼能解決需求方提出的問題。<blockquote><p>這裡很重要的一點是,常常需求方自己提出的解決方案並不能夠真的解決他們想要解決的問題,有時候需要來回確認才能夠得到真正的問題點,這裡是我目前覺得對我來說相對比較困難的部分。</p></blockquote></li><li>程式碼能與現有系統結合的天衣無縫。</li><li>其他程式設計師必須能讀懂你的程式碼。<blockquote><p>作者有提到說不只要寫好註解,程式碼也要經過精心錘鍊,讓程式碼可以表達程式設計意圖,作者表示這應該是程式設計師最難精通的一件事。</p></blockquote></li></ol></li><li>當你無法全神貫注地寫程式時,所寫的程式碼就有可能出錯。<blockquote><p>作者提到說他最糟糕的程式碼是在凌晨 3 點寫出來的程式碼,當時他覺得好極了但其實完全是個錯誤的設計,之後這段程式碼不停地回來肆虐他們,最後還變成團隊裡的一個笑話。</p></blockquote></li><li>如果感到心煩意亂,千萬不要寫程式。免強為之,最終只能回頭重做。相反,你必須找到一種方法來消除干擾,讓心緒平靜下來。</li></ul></li><li>如果我們能夠做些事情避免甚或消彌除錯工作,那是最理想不過的。</li><li>製造出許多 bug 的軟體開發人員也不專業。</li><li>管理延遲的要訣,便是早期檢測和保持透明。最糟糕的情況是,你一直都在告訴每個人你會按時完成工作,到最後期限來臨前你還在這樣說,但最終你只能讓他們大失所望。相反地,要根據目標定期衡量進度。</li><li>唯一能夠加快進度的方法便是縮減範圍。不要經受不住誘惑盲目衝刺。<blockquote><p>作者提到,如果可憐的開發人員在壓力之下最終屈服,同意盡力趕上截止日期,結局會十分悲慘。那些開發人員開始抄近路,會額外加班工作,抱者創造奇蹟的渺茫希望。這是製造災難的最佳秘訣,因為這種做法會給自己、給團隊以及利害相關的各方帶來了一個錯誤的期望。這樣每個人都可以避免面對真正的問題,並將做出『必要而艱難的決定』的時機不斷延後。<br>這邊即便最後做出來了,做出來的東西也會相當危險(不穩定,而且可能會有很多問題,要修改也可能相當難改),其中用了什麼方法抄近路也是蠻恐怖的一件事情,我想當每個開發人員在意識到自己快死了應該就是這樣的警訊?</p></blockquote></li><li>不應該採用額外加班工作的方案,除非以下三個條件都能滿足:<ol><li>你個人能擠出這些時間</li><li>短期加班,最多加班兩週</li><li>你的老闆要有後備方案,以防萬一加班措施失敗</li></ol></li><li>如果連續兩三個星期都要加班工作,則加班的措施必敗無疑。</li><li>在程式設計師所能表現的各種不專業行為中,最糟糕的是,明知道還沒有完成任務卻宣稱已經完成。</li><li>如果幫助唾手可得,卻讓自己一個人杵在那裡,是很不專業的表現。</li><li>花時間親自輔導手底下的年輕程式設計師,是資深程式設計師的專業職責所在。</li><li>向資深導師尋求輔導,也是年輕程式設計師的專業職責。</li></ul><blockquote><p>另外本章節作者也有提到他比較不推崇進入 flow 的狀態來寫程式,他推薦 pair programming 的方式,但我沒有百分百同意作者的想法,就給大家自己翻書來看看囉。</p></blockquote>]]></content>
<tags>
<tag> Clean Coder </tag>
</tags>
</entry>
<entry>
<title>無瑕的程式碼番外篇 - 説「不」/ 説 「是」</title>
<link href="/2022/01/31/the-clean-coder-two-three/"/>
<url>/2022/01/31/the-clean-coder-two-three/</url>
<content type="html"><![CDATA[<p><img src="image.jpg" alt="image"><br>大家好,我是 Cindy,最近發現無暇程式碼的番外篇蠻好看的,適合在職場打滾的工程師們看,比起 clean code,<a href="https://www.books.com.tw/products/0010598217" target="_blank" rel="noopener">the clean coder</a> 比較算是軟實力的部分,主要是在說身為一個專業的工程師應該要有怎麼樣的態度、原則與行動,這篇會是第二、三章節的重點及心得整理。想先看第一章重點心得的各位觀眾可以先點 <a href="/2022/01/28/the-clean-coder-one">這裡</a> 唷!</p><h2 id="説「不」"><a href="#説「不」" class="headerlink" title="説「不」"></a>説「不」</h2><p>能就是能,不能就是不能。不要說「試試看」。</p><p>這章節作者寫了一些故事,實際會發生在工作中的事情。一個強迫在預計時間準時上線,但是充滿問題不穩定的系統的另一場悲劇,這邊故事我覺得大家可以自己拿書來看看我就不詳細寫囉。</p><h3 id="畫重點:"><a href="#畫重點:" class="headerlink" title="畫重點:"></a>畫重點:</h3><ul><li>專業人士敢於說明真相而不屈從於權勢。專業人士有勇氣對他們的經理說「不」。</li><li>如果明知第二天前不可能完成功能,嘴上卻說「好的,我會試試看」,那麼便是你失職了。</li><li>竭盡所能捍衛目標,雙方才能得到可能的最好結果。</li><li>最重要的是要找到那個『共同目標』,而這往往有賴於『協商』。<blockquote><p>作者舉了一些例子,讓讀者理解工作中的雙方有沒有嘗試尋求『可接受的共同目標』,以及努力尋求『最佳的可能結果』的情況是什麼樣子。</p></blockquote></li></ul><p>最後作者也舉了一個小故事,在不合理的時程(兩週開發一個 iPhone app、一個後端 PHP、QA)中趕工寫出了不符合設模式的程式碼,最後辛苦了老半天也沒上線的悲慘故事,說明了說「是」的成本有多大多悲慘。</p><blockquote><p>老實說那些看不見程式碼的人可能無法理解,但糟糕的程式碼絕對會拖慢之後每一次的開發速度,畢竟寫程式碼的也是人,讀不懂前人寫的程式碼,又要如何去修改勒,所以勢必要先花時間理解囉,另外糟糕的程式碼也會很容易改 A 壞 B,改 C 壞 D,修改程式碼的成本是相當高的。如果我寫的程式碼是可以讓每個工程師輕鬆上手,這樣是不是表示哪天我發生了什麼意外,我的心血還是有持續延續的最大可能性勒。</p></blockquote><p><strong>委屈『專業原則』以求全,並非問題的解決之道。捨棄這些原則,只會製造出更多的麻煩。</strong></p><h2 id="説-「是」"><a href="#説-「是」" class="headerlink" title="説 「是」"></a>説 「是」</h2><p>口頭上說。心裡認真。付諸行動。</p><h3 id="畫重點:-1"><a href="#畫重點:-1" class="headerlink" title="畫重點:"></a>畫重點:</h3><ul><li>雖然你只能承諾自己能<strong>完全掌握</strong>的事。但你能承諾自己會採取一些<strong>具體行動</strong>來達成這個最終目標。<blockquote><p>常常我們會受限於與第三方合作,所以才無法肯定完成時間,但是我們可以承諾我們要採取的行動,例如:建立與第三方溝通的介面格式文件,並在本週與第三方開會確認格式是否要調整。</p></blockquote></li><li>弄清楚『目標能否達成』這件事,便是你可以採取的努力行動之一。</li><li>有些事情先前你可能沒預料到,這很現實。但如果你仍然希望自己不負眾望,那就趕緊去調整別人對你的期待,<strong>越快越好</strong>!</li><li>如果你能夠一直信守承諾,大家會認為你是『一名嚴謹負責的開發人員』。在我們這一行中,這也是最有價值的評價。</li><li>專業人士對自己的能力極限暸若指掌。</li><li>專業人士不需要對所有請求都回答「是」。不過,他們應該努力尋找創新的方法,盡可能做到有求必應。當專業人士給出肯定回答時,他們會使用『承諾用語』,已確保各方能無誤地明白及理解承諾的內容。</li></ul><blockquote><p>超級認可這兩章說的內容,捨棄專業只會造成更多的悲劇在未來發生。這兩章節雖然在講說「是」或「不」,但我覺得也有很重要的一點就是<strong>說出來</strong>。我最喜歡的也是直接又透明化的表達,最討厭那種迂迴又不直說的人惹,常常悲劇就是在沒說出來的地方發生,大家共勉之。下一篇會講關於寫程式這件事情,大家敬請期待!</p></blockquote><blockquote><p>往專業人士的路上前進,歡迎大家給我任何的回饋唷:)</p></blockquote>]]></content>
<tags>
<tag> Clean Coder </tag>
</tags>
</entry>
<entry>
<title>無瑕的程式碼番外篇 - 專業主義</title>
<link href="/2022/01/28/the-clean-coder-one/"/>
<url>/2022/01/28/the-clean-coder-one/</url>
<content type="html"><![CDATA[<p><img src="image.jpg" alt="image"><br>大家好,我是 Cindy,最近發現無暇程式碼的番外篇蠻好看的,適合在職場打滾的工程師們看,比起 clean code,<a href="https://www.books.com.tw/products/0010598217" target="_blank" rel="noopener">the clean coder</a> 比較算是軟實力的部分,主要是在說身為一個專業的工程師應該要有怎麼樣的態度、原則與行動。在寫此篇文章的時候我剛看完一到六章,本來想一口氣寫完,寫著寫著發現越寫越太多,所以我還是分一下篇幅好了,之後一到兩個章節寫一篇文章,這篇會是第一章節的重點及心得整理。</p><hr><p>這章節主要在告訴大家什麼是專業主義,也許直接看每個小節的標題(我列在下面囉),大家會覺得理所當然,但真的有多少人可以做到呢?</p><ul><li>清楚你要什麼</li><li>擔當責任</li><li>首先,不要做損害的事<ul><li>不要破壞軟體功能</li><li>讓 QA 找不出任何問題</li><li>要確信程式碼正常工作</li><li>自動化 QA</li><li>不要破壞結構</li></ul></li><li>職業道德<ul><li>了解你的領域</li><li>堅持學習</li><li>練習</li><li>協作</li><li>輔導</li><li>了解業務領域</li><li>與雇主/客戶保持一致</li><li>謙遜</li></ul></li></ul><p>作者在<strong>擔當責任</strong>的小節裡講述了他真的因為不負責任嚐盡苦頭的故事,這裡的不負責任也許就發生在大家的周遭,因為時程很趕,作者拼了命的在約定日前交付,最後卻是一場災難,<strong>因為沒有寫測試</strong>,程式上線後,本來固定夜間傳送報告的程式出錯了,最快的解決方法是回到上一個版本的程式碼,客戶遺失了整晚的資料,還無法使用原先承諾的新功能…。作者反思他不應該只顧保全自己的顏面,應該提早說出測試還沒完成的問題,老闆可能會不高興,但是客戶不會遺失資料…。</p><p>有時候我看這本書會覺得作者會不會有點兇 XD 不知道跟翻譯有沒有關,還是本來就這麼兇 XDDDDD</p><h2 id="畫重點"><a href="#畫重點" class="headerlink" title="畫重點"></a>畫重點</h2><ul><li>什麼樣的程式是有缺陷的?那些你沒把握的都是!</li><li>我不是在建議,我是在要求!你寫的每一行程式碼都要測試,就這樣!</li><li>要設計 『易於測試的程式碼』。</li><li>如果你希望自己的軟體靈活可變,那就應該時常修改它!</li><li>讓軟體保持固定不變是危險的!如果一直不重構程式碼,等到最後不得不重構時,你就會發現程式碼已經『僵化了』。<blockquote><p>這裡讓我想到之前看到蒼時大大寫的文章:<a href="https://vocus.cc/article/61ce942bfd89780001085c9d" target="_blank" rel="noopener">軟體是一種生物</a>,推薦大家去看看。</p></blockquote></li><li>不能銘記過去的人,注定重蹈先人的覆轍。<blockquote><p>我在這邊想到,如果可以站在巨人肩膀上,我們又為什麼硬要自幹程式碼然後去走那些巨人們踩過的雷勒?卡米大大的 <a href="https://etrexkuo.medium.com/%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E8%80%85%E7%9A%84%E5%9F%B9%E9%A4%8A-8fee43c76195" target="_blank" rel="noopener">軟體開發者的培養</a> 也有提到類似的概念,推薦給大家。</p></blockquote></li></ul><h2 id="每個專業軟體人必須精通的事項"><a href="#每個專業軟體人必須精通的事項" class="headerlink" title="每個專業軟體人必須精通的事項"></a>每個專業軟體人必須精通的事項</h2><ul><li>設計模式<ul><li><a href="https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8F%AF%E5%A4%8D%E7%94%A8%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%BD%AF%E4%BB%B6%E7%9A%84%E5%9F%BA%E7%A1%80" target="_blank" rel="noopener">GOF</a></li><li><a href="https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture" target="_blank" rel="noopener">POSA</a><blockquote><p>推薦大家這個網站 <a href="https://refactoring.guru/design-patterns" target="_blank" rel="noopener">DESIGN PATTERNS</a></p></blockquote></li></ul></li><li>設計原則<ul><li>SOLID<blockquote><p>我之前的文章有提到過唷,大家可以去 <a href="/2020/06/09/Practical-Object-Oriented-Design-in-Ruby">Ruby 物件導向設計實踐-敏捷入門</a> 看看。</p></blockquote></li><li>元件設計原則<blockquote><p>參考:</p><ul><li><a href="https://devs.tw/post/385" target="_blank" rel="noopener">元件如何正確使用 ? | 元件耦合性三大原則 : ADP、SDP、SAP</a></li><li><a href="https://devs.tw/post/438" target="_blank" rel="noopener">元件如何正確歸類 ? | 元件內聚性三大原則 : REP、CCP、CRP</a></li></ul></blockquote></li></ul></li><li>方法<ul><li><a href="https://zh.wikipedia.org/wiki/%E6%9E%81%E9%99%90%E7%BC%96%E7%A8%8B" target="_blank" rel="noopener">XP</a></li><li><a href="https://zh.wikipedia.org/wiki/Scrum" target="_blank" rel="noopener">Scrum</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%B2%BE%E7%9B%8A%E7%94%9F%E7%94%A2" target="_blank" rel="noopener">精益(Lean)</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%9C%8B%E6%9D%BF_(%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91" target="_blank" rel="noopener">看板(Kanban)</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%80%91%E5%B8%83%E6%A8%A1%E5%9E%8B" target="_blank" rel="noopener">瀑布</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%BB%93%E6%9E%84%E5%8C%96%E5%88%86%E6%9E%90" target="_blank" rel="noopener">結構化分析</a>及<a href="https://zh.wikipedia.org/wiki/%E7%B5%90%E6%A7%8B%E5%8C%96%E8%A8%AD%E8%A8%88" target="_blank" rel="noopener">結構化設計</a></li></ul></li><li>學科<ul><li>TDD<blockquote><p>推薦參考 <a href="https://www.youtube.com/watch?v=HFVjTFy42hI" target="_blank" rel="noopener">測試驅動開發 : 3 大法則 + 5 大好處 | 程式 x 開發 | 撰寫 單元測試 速度更快 【Gamma Ray 軟體工作室】</a></p></blockquote></li><li><a href="https://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1" target="_blank" rel="noopener">OOD</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%BB%93%E6%9E%84%E5%8C%96%E7%BC%96%E7%A8%8B" target="_blank" rel="noopener">結構化程式設計</a></li><li><a href="https://zh.wikipedia.org/wiki/%E6%8C%81%E7%BA%8C%E6%95%B4%E5%90%88" target="_blank" rel="noopener">Continuous integration</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%BB%93%E5%AF%B9%E7%BC%96%E7%A8%8B" target="_blank" rel="noopener">Pair Programming</a></li></ul></li><li>工具:<ul><li><a href="https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E5%BB%BA%E6%A8%A1%E8%AF%AD%E8%A8%80#%E5%9B%BE%E5%BD%A2" target="_blank" rel="noopener">UML圖</a></li><li><a href="https://zh.wikipedia.org/wiki/%E8%B3%87%E6%96%99%E6%B5%81%E7%A8%8B%E5%9C%96" target="_blank" rel="noopener">DFD圖</a></li><li><a href="https://en.wikipedia.org/wiki/Structure_chart" target="_blank" rel="noopener">結構圖</a></li><li><a href="https://zh.wikipedia.org/wiki/%E4%BD%A9%E7%89%B9%E9%87%8C%E7%B6%B2" target="_blank" rel="noopener">Petri 網路圖</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%8A%B6%E6%80%81%E5%9B%BE" target="_blank" rel="noopener">狀態遷移圖表</a></li><li><a href="https://zh.wikipedia.org/wiki/%E6%B5%81%E7%A8%8B%E5%9B%BE" target="_blank" rel="noopener">流程圖</a><br>這讓我想到上一份工作的時候,我們曾經用流程圖畫滿了整間辦公室,顆顆,主要是當時客戶的需求是巨複雜資料遷移,一個人會花相當多時間完成這個可執行資料遷移的程式,但客戶臨時要我們在極短的時間完成,所以為了能夠協同多位工程師開發,我們將要撰寫的程式,先用流程圖畫出來,然後劃分可以寫成各自獨立物件的範圍,再分配給每位成員完成各自的物件,最後再將物件組裝起來。(物件導向的極致?</li><li><a href="https://en.wikipedia.org/wiki/Decision_table" target="_blank" rel="noopener">決策表</a></li></ul></li></ul><blockquote><p>花了一些時間查資料,說明我並非所有列出來的事項都相當理解,反省中,下一篇會介紹工程師什麼時候說是,什麼時候說不,歡迎大家繼續看下去。</p></blockquote>]]></content>
<tags>
<tag> Clean Coder </tag>
</tags>
</entry>
<entry>
<title>Rails + Drone CI with ssh CD</title>
<link href="/2021/11/25/drone-ci-with-ssh-cd/"/>
<url>/2021/11/25/drone-ci-with-ssh-cd/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近開始在用 <a href="https://www.drone.io" target="_blank" rel="noopener">Drone CI</a> - 一款用 <a href="https://zh.wikipedia.org/wiki/Go" target="_blank" rel="noopener">Go</a> 寫的 CI platform,紀錄一下,我練習用自己的電腦搭配 <a href="https://dashboard.ngrok.com" target="_blank" rel="noopener">ngrok</a> 架一個暫時的 CI server,配上 Github 跑測試和部署的實作過程。((因為練習才這樣做唷,正常會架在 GCP 之類的地方,或是可以直接用 <a href="https://cloud.drone.io/welcome" target="_blank" rel="noopener">cloud drone</a> 不需要自架 server 的方案。))</p><h2 id="用-docker-ngrok-在-local-起-drone-server"><a href="#用-docker-ngrok-在-local-起-drone-server" class="headerlink" title="用 docker + ngrok 在 local 起 drone server"></a>用 docker + ngrok 在 local 起 drone server</h2><ol><li><p>安裝 ngrok<br> <code>brew install --cask ngrok</code></p></li><li><p>註冊 <a href="https://dashboard.ngrok.com" target="_blank" rel="noopener">ngrok</a></p></li><li><p>依照官網說明連結帳戶 <code>ngrok authtoken xxxxx</code></p></li><li><p>將 ngrok run 在 port 80<br> <code>ngrok http 80</code></p></li><li><p>在 github 新增 OAuth Apps<br><img src="https://i.imgur.com/aiYHeC8.png" alt=""></p></li><li><p>新增一組密碼<br> <code>openssl rand -hex 16</code></p></li><li><p>新增 docker-compose.yml</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">"3.9"</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="attr">drone-server:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">drone/drone:2</span></span><br><span class="line"> <span class="attr">container_name:</span> <span class="string">drone-server</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">443</span><span class="string">:443</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">/var/lib/drone:/var/lib/drone/</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_GITHUB_CLIENT_ID=xxx</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_GITHUB_CLIENT_SECRET=xxx</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_RPC_SECRET=ooo</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_SERVER_HOST=xxxxxx.ngrok.io</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_SERVER_PROTO=https</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_DATABASE_DATASOURCE=/var/lib/drone/drone.sqlite</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_DATABASE_DRIVER=sqlite3</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_DEBUG=true</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_LOGS_DEBUG=true</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_LOGS_TRACE=true</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">drone-runner:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">drone/drone-runner-docker:1</span></span><br><span class="line"> <span class="attr">container_name:</span> <span class="string">drone-runner</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">3000</span><span class="string">:3000</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">depends_on:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">drone-server</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">/var/run/docker.sock:/var/run/docker.sock</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_RPC_PROTO=http</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_RPC_HOST=xxxxxx.ngrok.io</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_RPC_SECRET=ooo</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_RUNNER_CAPACITY=2</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_DEBUG=true</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_LOGS_DEBUG=true</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DRONE_LOGS_TRACE=true</span></span><br></pre></td></tr></table></figure><p>執行 <code>docker-compose up -d</code>,用 <code>docker ps</code> 就可以檢查 docker container 是不是有跑起來囉。</p><p>註:如果不想將私密資訊直接寫在 <code>docker-compose.yml</code>,可以寫在 <a href="https://docs.docker.com/compose/environment-variables/#the-env-file" target="_blank" rel="noopener">.env</a> 的檔案,設定 docker 的環境變數。</p></li><li><p>輸入 ngrok 的網址確認 server 成功起來可以使用</p></li><li><p>確認 runner log<br> <code>docker logs drone-runner</code></p></li><li><p>在自架的 drone server 網站選擇要跑 CI 的專案,啟用後這邊會自動在 Github 建立 Webhook,之後如果想改 Webhook 的規則也可以直接在 Github 修改</p></li></ol><h2 id="自動測試"><a href="#自動測試" class="headerlink" title="自動測試"></a>自動測試</h2><p>在專案新增 <code>.drone.yml</code></p> <figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">kind:</span> <span class="string">pipeline</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">docker</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">default</span></span><br><span class="line"></span><br><span class="line"><span class="attr">platform:</span></span><br><span class="line"> <span class="attr">os:</span> <span class="string">linux</span></span><br><span class="line"> <span class="attr">arch:</span> <span class="string">arm64</span></span><br><span class="line"></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">ruby:3.0.0</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="attr">RAILS_ENV:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">DATABASE_URL:</span> <span class="string">"postgresql://postgres:postgres@postgres:5432/test_drone_test"</span></span><br><span class="line"> <span class="attr">RAILS_MASTER_KEY:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">rails_master_key</span></span><br><span class="line"> <span class="attr">commands:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">install</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">exec</span> <span class="string">rake</span> <span class="string">db:create</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">exec</span> <span class="string">rake</span> <span class="string">db:migrate</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">exec</span> <span class="string">rspec</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">postgres</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">postgres:13-alpine</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="attr">POSTGRES_HOST_AUTH_METHOD:</span> <span class="string">trust</span></span><br></pre></td></tr></table></figure><p> 這樣就可以跑測試囉!</p><h2 id="ssh-自動部署"><a href="#ssh-自動部署" class="headerlink" title="ssh 自動部署"></a>ssh 自動部署</h2><p>用 ssh 的方式進行部署,設定檔的寫法類似下面這樣:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">kind:</span> <span class="string">pipeline</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">docker</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">default</span></span><br><span class="line"></span><br><span class="line"><span class="string">...</span></span><br><span class="line"></span><br><span class="line"><span class="attr">definitions:</span></span><br><span class="line"> <span class="attr">env-settings:</span> <span class="string">&env-settings</span></span><br><span class="line"> <span class="attr">host:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">host</span></span><br><span class="line"> <span class="attr">username:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">username</span></span><br><span class="line"> <span class="attr">key:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">ssh_key</span></span><br><span class="line"> <span class="attr">port:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">ssh_port</span></span><br><span class="line"> <span class="comment"># script_stop: 只要其中一個 script 失敗,後面的 script 就不會執行</span></span><br><span class="line"> <span class="attr">script_stop:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment"># 由於 appleboy/drone-ssh 的 issue,需要重新讀取 rvm 的設定,才不會出現 bundle: command not found 的 error</span></span><br><span class="line"> <span class="comment"># issue: https://github.com/appleboy/ssh-action/issues/21</span></span><br><span class="line"> <span class="comment"># rvm info: https://rvm.io/rvm/install#3-reload-shell-configuration-amp-test</span></span><br><span class="line"> <span class="attr">scripts:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">script:</span> <span class="string">&export-rvm</span> <span class="string">export</span> <span class="string">PATH="$PATH:$HOME/.rvm/bin"</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">script:</span> <span class="string">&reload-rvm-setting</span> <span class="string">source</span> <span class="string">~/.rvm/scripts/rvm</span></span><br><span class="line"></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line"> <span class="string">...</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">deploy</span></span><br><span class="line"> <span class="comment"># image ref: http://plugins.drone.io/appleboy/drone-ssh</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">appleboy/drone-ssh</span></span><br><span class="line"> <span class="attr">settings:</span></span><br><span class="line"> <span class="string"><<:</span> <span class="string">*env-settings</span></span><br><span class="line"> <span class="attr">script:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">cd</span> <span class="string">RepoName</span></span><br><span class="line"> <span class="comment"># ref: https://docs.drone.io/pipeline/environment/reference/drone-tag/</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">git</span> <span class="string">fetch</span> <span class="string">origin</span> <span class="string">${DRONE_TAG}:${DRONE_TAG}</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">git</span> <span class="string">checkout</span> <span class="string">${DRONE_TAG}</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">*export-rvm</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">*reload-rvm-setting</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">install</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">bundle</span> <span class="string">exec</span> <span class="string">rails</span> <span class="string">db:migrate</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">sudo</span> <span class="string">service</span> <span class="string">sidekiq</span> <span class="string">restart</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">passenger-config</span> <span class="string">restart-app</span> <span class="string">/home/deployer/RepoName</span></span><br><span class="line"> <span class="attr">when:</span></span><br><span class="line"> <span class="attr">ref:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">refs/tags/v*</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">notification</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">plugins/slack</span></span><br><span class="line"> <span class="attr">settings:</span></span><br><span class="line"> <span class="attr">webhook:</span></span><br><span class="line"> <span class="attr">from_secret:</span> <span class="string">slack_webhook</span></span><br><span class="line"> <span class="attr">channel:</span> <span class="string">devops</span></span><br><span class="line"> <span class="attr">when:</span></span><br><span class="line"> <span class="attr">status:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">success</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">failure</span></span><br><span class="line"></span><br><span class="line"><span class="attr">trigger:</span></span><br><span class="line"> <span class="attr">ref:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">refs/heads/develop</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">refs/pull/**</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">refs/tags/v*</span></span><br></pre></td></tr></table></figure><h2 id="其他相關參考資料"><a href="#其他相關參考資料" class="headerlink" title="其他相關參考資料"></a>其他相關參考資料</h2><ul><li><a href="https://docs.drone.io/server/provider/github" target="_blank" rel="noopener">Drone CI with GitHub</a></li><li><a href="https://iter01.com/477903.html" target="_blank" rel="noopener">DroneCI+Github入坑指北</a></li><li><a href="https://mgleon08.github.io/blog/2018/09/17/drone/" target="_blank" rel="noopener">Drone</a></li><li>sidekiq - <a href="https://github.com/mperham/sidekiq/wiki/Deployment#running-your-own-process" target="_blank" rel="noopener">Running your own Process</a></li><li><a href="https://ihower.tw/rails/deployment.html" target="_blank" rel="noopener">網站佈署</a></li><li><a href="https://www.phusionpassenger.com/library/admin/nginx/restart_app.html#restarting-applications-on-passenger-+-nginx" target="_blank" rel="noopener">Restarting applications</a></li><li>Passenger 是目前佈署 Ruby on Rails 最好用、設定最簡單的方式,它是一套 Apache 和 Nginx 的擴充模組,可以直接支援 Rails 或任何 Rack 應用程式。<ul><li><a href="https://github.com/phusion/passenger/blob/44ab81c9fe45456415bd5312c3d695e9f225730f/src/agent/Core/ApplicationPool/Group/SpawningAndRestarting.cpp#L370" target="_blank" rel="noopener">source code</a></li></ul></li><li><a href="https://git-scm.com/book/zh-tw/v2/Git-Internals-Git-References" target="_blank" rel="noopener">Git Internals - Git References</a></li></ul>]]></content>
<tags>
<tag> Rails </tag>
<tag> Drone CI </tag>
<tag> ssh CD </tag>
</tags>
</entry>
<entry>
<title>Git 小技巧分享</title>
<link href="/2021/11/06/About-git-skill/"/>
<url>/2021/11/06/About-git-skill/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,今天想記錄一下我曾經用的 Git 小技巧,分享給大家,也歡迎大家留言跟我分享其他小技巧唷~<br>Git 是現今軟體開發常用的分散式版本控制系統,主要會將檔案的修改記錄(類似 snapshot)下來,讓我們可以在修改之間穿梭,想知道更詳細的朋友們可以參考<a href="https://gitbook.tw" target="_blank" rel="noopener">為你自己學 Git</a>這本書 (純分享無業配 XD)</p><h2 id="情境一"><a href="#情境一" class="headerlink" title="情境一"></a>情境一</h2><p>開發模式: <code>alpha 內部測試環境 -> beta 外部測試環境 -> production 正式環境</code></p><p>某天某重要人士(可能是客戶之類的)希望直接從 beta 先拿掉某個功能,將 beta 已經測試完成剩下的功能全都上 production,這邊先不討論 feature flag,單純指如果使用 git 可以怎麼達成這樣的需求呢?</p><p>這個時候,<a href="https://git-scm.com/docs/git-revert" target="_blank" rel="noopener">git revert</a> 就是個好用的指令拉,當我們使用 git revert 的時候我們會得到跟被 revert 那顆 commit 完全相反的一顆新的 commit,例如 commit SHA1 是 <em>新增 hello</em>,<code>git revert SHA1</code> 就會得到一顆新的 commit 是 <em>刪除 hello</em>,但是如果今天某功能的修改有很多顆 commit 不是會非常麻煩嗎?大家也不用擔心,我們可以直接 revert merge 的那顆 commit 唷,但這邊會需要注意 merge 的那顆 commit 會有兩個 parent,假如今天是 A branch merge into B branch,這顆 merge 的 commit 的 parent 一個是 B branch 的上一顆 commit(圖1 的 P1),另一個則是 A branch 自己的上一顆 commit(圖1 的 P2),<code>git log --oneline --graph</code> 類似下圖1,這時如果我們是輸入 <code>git revert SHA1</code> 就會出現錯誤,因為 git 不知道我們想要 revert 的是跟哪一個 parent 比較之後的結果,這時候我們只要輸入 <code>git revert SHA1 -m 1</code> 這樣的話就會是 revert 掉跟 B branch 上一顆 commit 比較之後的結果囉,反之亦然。</p><p>圖1:<br><img src="https://i.imgur.com/CTrXDAd.png" alt="example" width="200"/></p><h2 id="情境二"><a href="#情境二" class="headerlink" title="情境二"></a>情境二</h2><p>開發模式: <code>測試環境 -> production 正式環境</code></p><p>某個已經上測試環境的功能需要緊急上線到 production,但其中測試環境中還有尚未完成測試的其他功能,這時候可以從 production 分支(可能是 main 或 master,或先開一個分支做再發 pr),執行 <a href="https://git-scm.com/docs/git-cherry-pick" target="_blank" rel="noopener">git cherry-pick</a> 可以將需要緊急上線的功能撿起來,這邊跟情境一一樣如果覺得 commit 太多顆很麻煩,可以直接 cherry-pick merge 的那顆 commit,一樣要跟 git 說我們要比較的 parent 是 1 還是 2 囉!所以指令會是 <code>git cherry-pick SHA1 -m 1</code> 或 <code>git cherry-pick SHA1 -m 2</code>。</p><h2 id="衝突"><a href="#衝突" class="headerlink" title="衝突"></a>衝突</h2><p>在執行 git 指令的時候如果有衝突發生的話也不用擔心,不知道要怎麼做的時候可以先 <code>git status</code> 會看到很多提示唷!想當年第一次執行 <a href="https://git-scm.com/docs/git-rebase" target="_blank" rel="noopener">git rebase</a> 之後還真不知道要怎麼辦呢!簡單說一下,發生衝突大概會是 <code>git status</code>看哪些檔案有衝突 -> 解衝突 -> git add 解衝突的檔案(或如果要刪除就是執行 rm) -> 解完衝突的話通常是 <code>git xxx --continue</code>(例如 <code>git rebase --continue</code> 等等)。</p><h2 id="感想"><a href="#感想" class="headerlink" title="感想"></a>感想</h2><p>Git 其實很好上手,但真的要熟練其實也不是那麼簡單,因為很多時候我們都只會需要會 <code>git add</code>、<code>git commit</code>、<code>git push</code>、<code>git pull</code>、<code>git checkout</code> 幾個常用的指令就好了,遇到特殊問題還是會需要想一下,因為很多時候其實是有很多方式可以解決,可能也會不確定怎麼做才會是當下最適合的做法,顆顆。</p><h2 id="其他參考資料"><a href="#其他參考資料" class="headerlink" title="其他參考資料"></a>其他參考資料</h2><ul><li><a href="https://gitbook.tw/chapters/rewrite-history/reset-revert-and-rebase.html" target="_blank" rel="noopener">Reset、Revert 跟 Rebase 指令有什麼差別?</a></li><li><a href="https://gitbook.tw/chapters/branch/fix-conflict.html" target="_blank" rel="noopener">合併發生衝突了,怎麼辦?</a></li></ul>]]></content>
<tags>
<tag> Git </tag>
</tags>
</entry>
<entry>
<title>Rails Websocket 後端實作紀錄</title>
<link href="/2021/10/03/rails-websocket/"/>
<url>/2021/10/03/rails-websocket/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近有做了 Rails Websocket 的功能,想說做個紀錄,以下文章重點會放在實作面,雖然是這麼說,但還是查一下什麼是 Websocket 好像比較正確。</p><h2 id="Websocket"><a href="#Websocket" class="headerlink" title="Websocket"></a>Websocket</h2><p>WebSocket 是一種網路傳輸協定,簡單講就是早期網路通訊只考慮到由 client 端發送請求給 server 端,這種單向的傳輸,當有雙向傳輸的需求(ex: 推播功能)實作上會變得比較複雜,所以後來就出現 WebSocket 的通訊協定了!(當然詳細過程並不是這麼簡單 XD),可以參考<a href="https://zh.wikipedia.org/wiki/WebSocket" target="_blank" rel="noopener">維基百科</a>的說明。</p><h2 id="Rails-6-實作-Websocket"><a href="#Rails-6-實作-Websocket" class="headerlink" title="Rails 6 實作 Websocket"></a>Rails 6 實作 Websocket</h2><p>在 Rails 5 之後有了 ActionCable 讓我們可以更方便的在 Rails 中做 Websocket 的應用。</p><ol><li><p>首先 new 一個新的 Rails 專案,並進行前置作業</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rails new test-action-cable-api --database=postgresql -T --api</span><br></pre></td></tr></table></figure><p>在 output 中其實就會看到 rails 自動幫我們產生了一些跟 ActionCable 有關的檔案</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"> create app/channels/application_cable/channel.rb</span><br><span class="line"> create app/channels/application_cable/connection.rb</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"> create config/cable.yml</span><br><span class="line">...</span><br></pre></td></tr></table></figure><ul><li>建立 User model & db schema<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rails generate model User name email password_digest</span><br><span class="line">rails db:create</span><br><span class="line">rails db:migrate</span><br></pre></td></tr></table></figure></li><li>修改 User model<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> < ApplicationRecord</span></span><br><span class="line"> has_secure_password</span><br><span class="line"></span><br><span class="line"> validates <span class="symbol">:name</span>, <span class="symbol">presence:</span> <span class="literal">true</span></span><br><span class="line"> validates <span class="symbol">:email</span>, <span class="symbol">presence:</span> <span class="literal">true</span>, <span class="symbol">uniqueness:</span> <span class="literal">true</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>在 <code>Gemfile</code> uncomment redis 和 bcrypt,接著執行 bundle install 進行套件安裝<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Use Redis adapter to run Action Cable in production</span></span><br><span class="line">gem <span class="string">'redis'</span>, <span class="string">'~> 4.0'</span></span><br><span class="line"><span class="comment"># Use Active Model has_secure_password</span></span><br><span class="line">gem <span class="string">'bcrypt'</span>, <span class="string">'~> 3.1.7'</span></span><br></pre></td></tr></table></figure></li><li>在 rails c 先 create 一個測試的 user<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User.create!(<span class="symbol">name:</span> <span class="string">"cindy"</span>, <span class="symbol">email:</span> <span class="string">"[email protected]"</span>, <span class="symbol">password:</span> <span class="string">"12345678"</span>)</span><br></pre></td></tr></table></figure></li></ul></li><li><p>打開 <code>config/cable.yml</code>,修改 adapter,參考<a href="https://guides.rubyonrails.org/action_cable_overview.html#subscription-adapter" target="_blank" rel="noopener">文件</a>,我們可以使用 redis 當作 adapter。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">development:</span></span><br><span class="line"> <span class="attr">adapter:</span> <span class="string">redis</span></span><br><span class="line"> <span class="attr">url:</span> <%=<span class="ruby"> ENV.fetch(<span class="string">"REDIS_URL"</span>) { <span class="string">"redis://localhost:6379/1"</span> } </span>%></span><br><span class="line"></span><br><span class="line"><span class="attr">test:</span></span><br><span class="line"> <span class="attr">adapter:</span> <span class="string">test</span></span><br><span class="line"></span><br><span class="line"><span class="attr">production:</span></span><br><span class="line"> <span class="attr">adapter:</span> <span class="string">redis</span></span><br><span class="line"> <span class="attr">url:</span> <%=<span class="ruby"> ENV.fetch(<span class="string">"REDIS_URL"</span>) { <span class="string">"redis://localhost:6379/1"</span> } </span>%></span><br><span class="line"> <span class="attr">channel_prefix:</span> <span class="string">test_action_cable_api_production</span></span><br></pre></td></tr></table></figure></li><li><p>打開 <code>config/application.rb</code>,新增 ActionCable 的 mount path,也就是要進行 Websocket 連線時的 path。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">config.action_cable.mount_path = <span class="string">'/cable'</span></span><br></pre></td></tr></table></figure></li><li><p>打開 <code>config/environments/development.rb</code>,在開發環境將同源限制移除。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Uncomment if you wish to allow Action Cable access from any origin.</span></span><br><span class="line">config.action_cable.disable_request_forgery_protection = <span class="literal">true</span></span><br></pre></td></tr></table></figure></li><li><p>打開 <code>config/environments/production.rb</code>,設定 allowed request origins。(有需要跨不同源網站互動的時候再設定)</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">config.action_cable.allowed_request_origins = [ <span class="string">'http://example.com'</span>, <span class="regexp">/http:\/\/example.*/</span> ]</span><br></pre></td></tr></table></figure></li><li><p>打開 <code>app/channels/application_cable/connection.rb</code>,作為建立 connection 時身份認證使用</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">ApplicationCable</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Connection</span> < ActionCable::Connection::<span class="title">Base</span></span></span><br><span class="line"> identified_by <span class="symbol">:current_user</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">connect</span></span></span><br><span class="line"> <span class="keyword">self</span>.current_user = find_verified_user</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> private</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 通常不太會直接把 user id 放在 cookies,只是方便暫時這樣寫歐</span></span><br><span class="line"> <span class="comment"># 暫時測試時也可以先直接寫死 id</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_verified_user</span></span></span><br><span class="line"> <span class="keyword">if</span> verified_user = User.find_by(<span class="symbol">id:</span> cookies[<span class="symbol">:user_id</span>])</span><br><span class="line"> verified_user</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> reject_unauthorized_connection</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li><p>新增一個 channel</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rails g channel test</span><br></pre></td></tr></table></figure><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TestChannel</span> < ApplicationCable::Channel</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">subscribed</span></span></span><br><span class="line"> stream_from <span class="string">'test_channel'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">unsubscribed</span></span></span><br><span class="line"> stop_all_streams</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li><p>簡單地進行測試<br>先在 terminal 輸入 rails s,將 Rails server run 起來,接著可以在瀏覽器 DevTools 的 console 使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications" target="_blank" rel="noopener">WebSocket API</a> 或 <a href="https://blog.postman.com/postman-supports-websocket-apis" target="_blank" rel="noopener">postman</a> 進行 websocket 連線,接著進行頻道的訂閱</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"subscribe"</span>,</span><br><span class="line"> <span class="attr">"identifier"</span>: <span class="string">"{\"channel\":\"TestChannel\"}"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>rails log 如下表示有成功訂閱 test 頻道</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">TestChannel is transmitting the subscription confirmation</span><br><span class="line">TestChannel is streaming from test_channel</span><br></pre></td></tr></table></figure><p>接著在 rails console 用以下程式碼發送通知到 test channel</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ActionCable.server.broadcast(<span class="string">'test_channel'</span>, <span class="string">'test'</span>)</span><br></pre></td></tr></table></figure><p>rails log</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TestChannel transmitting "test" (via streamed from test_channel)</span><br></pre></td></tr></table></figure><p>postman 會收到訊息<br><img src="https://i.imgur.com/hOYXyAF.png" alt=""></p><p>如此一來就可以將 broadcast 寫在需要發送訊息到 channel 的任何地方囉(通常可能是 Job 裡)。</p><p>另外若使用 js WebSocket API 加上 cookies 測試範例如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.cookie = <span class="string">'user_id='</span> + <span class="number">1</span> + <span class="string">'; path=/'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> WebSocket(</span><br><span class="line"> <span class="string">'ws://localhost:3000/cable'</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>從 Network 觀看結果<br><img src="https://i.imgur.com/kqvkhOb.png" alt=""></p></li></ol><h2 id="需要注意的地方"><a href="#需要注意的地方" class="headerlink" title="需要注意的地方"></a>需要注意的地方</h2><ul><li>在做身份認證的其中一個方式是可以放 token 在 header,但對瀏覽器來說用 JavaScript 在 Websocket 傳送客製化的 header 是困難的,參考: <a href="https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api" target="_blank" rel="noopener">HTTP headers in Websockets client API</a>。</li><li>用 cookie 做身份認證的話,如果說前後端不同源也是會有問題。</li><li>如果用 postman 測試 production 環境,也是會遇到不同源的問題。</li></ul><h2 id="參考資料"><a href="#參考資料" class="headerlink" title="參考資料"></a>參考資料</h2><ul><li><a href="https://ihower.tw/rails/actioncable.html" target="_blank" rel="noopener">Action Cable 即時通訊</a></li><li><a href="https://guides.rubyonrails.org/action_cable_overview.html" target="_blank" rel="noopener">Action Cable Overview</a></li><li><a href="https://medium.com/@the.asantiagojr/how-to-setup-actioncable-with-a-rails-api-backend-1f1807c2d908" target="_blank" rel="noopener">How to Setup ActionCable with a Rails API Backend</a></li><li><a href="https://medium.com/codequest/actioncable-in-rails-api-f087b65c860d" target="_blank" rel="noopener">ActionCable in Rails API</a></li><li><a href="https://ruby-china.org/topics/30494" target="_blank" rel="noopener">Rails 聊一聊ActionCable 背后的技术</a></li><li><a href="https://pastleo.me/post/20210115-web-p2p-chatroom" target="_blank" rel="noopener">在 Web 上建構半分散式網路、聊天室 - 使用 WebRTC & WebSocket</a> (西瓜大大的文章,貼在這裡與大家分享 XD)</li><li><a href="https://github.com/cindyliu923/test-action-cable-api" target="_blank" rel="noopener">這篇文章的範例程式碼</a></li></ul>]]></content>
<tags>
<tag> Ruby </tag>
<tag> Rails </tag>
<tag> Websocket </tag>
<tag> ActionCable </tag>
</tags>
</entry>
<entry>
<title>MacBook Pro M1 開箱使用紀錄</title>
<link href="/2021/07/17/macbook-pro-m1/"/>
<url>/2021/07/17/macbook-pro-m1/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,勇者我最近換了 MacBook Pro M1,想把各種安裝過程記錄下來,也希望可以幫助到大家 :)<br>不過因為是紀錄可能蠻瑣碎的,有需要的人可以直接跳到自己有需要的段落歐,另外如果之後還有遇到其他問題,這篇應該會持續更新。</p><p><img src="https://i.imgur.com/02o6t9x.png" alt=""></p><h1 id="目錄"><a href="#目錄" class="headerlink" title="目錄"></a>目錄</h1><ul><li><a href="/2021/07/17/macbook-pro-m1/#安裝各種-APP">安裝各種 APP</a></li><li><a href="/2021/07/17/macbook-pro-m1/#iTerm">iTerm</a><ul><li><a href="/2021/07/17/macbook-pro-m1/#安裝-for-m1-arm-的-homebrew">安裝 for m1(arm) 的 homebrew</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-for-intel-x86-的-homebrew">安裝 for intel(x86) 的 homebrew</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-oh-my-zsh">安裝 oh my zsh</a></li><li><a href="/2021/07/17/macbook-pro-m1/#設定-Sublime-Text">設定 Sublime Text</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-rbenv">安裝 rbenv</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-nvm">安裝 nvm</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-yarn">安裝 yarn</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-MySQL-amp-PostgreSQL-amp-Redis">安裝 MySQL & PostgreSQL & Redis</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝-rails">安裝 rails</a></li><li><a href="/2021/07/17/macbook-pro-m1/#安裝其他套件">安裝其他套件</a></li></ul></li></ul><h1 id="安裝各種-APP"><a href="#安裝各種-APP" class="headerlink" title="安裝各種 APP"></a>安裝各種 APP</h1><ul><li>瀏覽器<ul><li><a href="https://www.google.com/intl/zh-TW/chrome/?brand=FKPE&gclid=EAIaIQobChMIhd2atdG08QIVjX0rCh2pVA9wEAAYASAAEgJyBvD_BwE&gclsrc=aw.ds" target="_blank" rel="noopener">chrome</a></li></ul></li><li>通訊用<ul><li><a href="https://apps.apple.com/tw/app/slack-for-desktop/id803453959?mt=12" target="_blank" rel="noopener">slack</a></li><li><a href="https://apps.apple.com/tw/app/line/id539883307?mt=12" target="_blank" rel="noopener">line</a></li><li><a href="https://apps.apple.com/tw/app/telegram/id747648890?mt=12" target="_blank" rel="noopener">telegram</a></li><li><a href="https://discord.com/download" target="_blank" rel="noopener">discord</a></li><li><a href="https://mattermost.com/download" target="_blank" rel="noopener">mattermost</a></li><li><a href="https://www.skype.com/zh-Hant/get-skype/" target="_blank" rel="noopener">skype</a></li><li>視訊用<ul><li><a href="https://zoom.us/download" target="_blank" rel="noopener">zoom</a></li></ul></li></ul></li><li>聽音樂<ul><li><a href="https://www.spotify.com/tw/download/mac/" target="_blank" rel="noopener">stopify</a><ul><li>遇到畫面全黑無法使用的問題,解決辦法:<ol><li>前往 <code>~/資源庫/Application Support/Spotify</code></li><li>打開 <code>prefs</code></li><li>將 <code>language="zh-TW"</code> 放進檔案中,存擋</li><li>重開 Spotify</li></ol></li><li>參考文章: <a href="https://pttweb.tw/s/1lPJZG" target="_blank" rel="noopener">[求救] MacBook Air 2020 Spotify開啟黑畫面</a></li></ul></li></ul></li><li>文件用<ul><li><a href="https://typora.io/#download" target="_blank" rel="noopener">typora</a></li></ul></li><li>繪圖<ul><li><a href="https://github.com/jgraph/drawio-desktop/releases" target="_blank" rel="noopener">draw.io</a></li></ul></li><li>開發用<ul><li>API<ul><li><a href="https://www.postman.com/downloads" target="_blank" rel="noopener">postman</a></li></ul></li><li>Database<ul><li><a href="https://tableplus.com" target="_blank" rel="noopener">tableplus</a><ul><li>免費:一次開 2 個 tab 操作</li></ul></li><li><a href="https://apps.apple.com/tw/app/sqlpro-studio/id985614903?mt=12" target="_blank" rel="noopener">SQLPro Studio</a><ul><li>可免費試用一週</li></ul></li><li><a href="https://www.dbvis.com/features" target="_blank" rel="noopener">DbVisualizer</a><ul><li>免費:可將現存的 DB 畫出 <a href="https://zh.wikipedia.org/wiki/ER%E6%A8%A1%E5%9E%8B" target="_blank" rel="noopener">ERD</a></li></ul></li></ul></li><li>Git<ul><li><a href="https://www.sourcetreeapp.com" target="_blank" rel="noopener">sourcetree</a></li></ul></li><li>編輯器<ul><li><a href="https://www.sublimetext.com" target="_blank" rel="noopener">sublime</a></li><li><a href="https://code.visualstudio.com/download" target="_blank" rel="noopener">VSCode</a></li></ul></li><li>終端機(terminal)<ul><li><a href="https://iterm2.com/downloads.html" target="_blank" rel="noopener">iTerm2</a></li></ul></li><li><a href="https://docs.docker.com/docker-for-mac/apple-silicon" target="_blank" rel="noopener">Docker</a><ul><li><a href="https://docs.docker.com/docker-for-mac/apple-silicon/#known-issues" target="_blank" rel="noopener">已知問題</a></li></ul></li><li>影片錄製及播放<ul><li><a href="https://obsproject.com/download" target="_blank" rel="noopener">OBS</a></li><li><a href="https://apps.apple.com/tw/app/mkplayer-mkv-media-player/id1335612105?mt=12" target="_blank" rel="noopener">MKPlayer</a></li></ul></li><li><a href="https://apps.apple.com/tw/app/xcode/id497799835?mt=12" target="_blank" rel="noopener">Xcode</a></li></ul></li></ul><h1 id="iTerm"><a href="#iTerm" class="headerlink" title="iTerm"></a>iTerm</h1><p>m1 的 shell 預設是 zsh</p><ul><li>安裝 <a href="https://brew.sh/index_zh-tw" target="_blank" rel="noopener">homebrew</a></li><li><a href="https://docs.brew.sh/Installation" target="_blank" rel="noopener">Homebrew Documentation</a></li></ul><h1 id="安裝-for-m1-arm-的-homebrew"><a href="#安裝-for-m1-arm-的-homebrew" class="headerlink" title="安裝 for m1(arm) 的 homebrew"></a>安裝 for m1(arm) 的 homebrew</h1><ol><li>在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 系統會將 homebrew 安裝在 /opt/homebrew</span></span><br><span class="line">/bin/bash -c <span class="string">"<span class="variable">$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)</span>"</span></span><br></pre></td></tr></table></figure></li><li>在 terminal 執行以下指令,確認 path <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$path</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># /usr/local/bin ...</span></span><br></pre></td></tr></table></figure></li><li>在 terminal 執行以下指令,打開 .zshrc <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">open ~/.zshrc</span><br></pre></td></tr></table></figure><ul><li>將以下新增至 .zshrc 檔案中 <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 由於第 2 步驟已經確定此路徑不在 path 中,</span><br><span class="line"># 故需要加入此路徑才能讓用 brew 指令安裝的套件在執行的時候被找到</span><br><span class="line">export PATH=/opt/homebrew/bin:$PATH</span><br><span class="line"></span><br><span class="line"># 讓 brew 的指令會去找安裝在 /opt/homebrew/ 的 homebrew</span><br><span class="line">alias brew='/opt/homebrew/bin/brew'</span><br></pre></td></tr></table></figure><ul><li>參考資料: <a href="https://www.spreered.com/bootstrap_iterm_zsh" target="_blank" rel="noopener">客製我的 CLI - 終於稍微搞懂 iTerm + ZSH</a></li></ul></li></ul></li><li>確認有成功安裝<ul><li>command+T 重開一個分頁,或在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line"><span class="built_in">exec</span> <span class="variable">$SHELL</span></span><br></pre></td></tr></table></figure><ul><li>由於要確認 terminal 有吃到 .zshrc 最新的設定,所以需要做上面步驟<ul><li>最後我還是都直接重開一個分頁,因為重新執行的時候,如果 path 改來改去,曾經有新增的 path 會一直加上去</li></ul></li></ul></li><li>在 terminal 執行 brew config 可正常執行 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">brew config</span><br><span class="line"></span><br><span class="line"><span class="comment">## ...</span></span><br><span class="line"><span class="comment">## macOS: 11.4-arm64</span></span><br><span class="line"><span class="comment">## ...</span></span><br><span class="line"><span class="comment">## Rosetta 2: false</span></span><br></pre></td></tr></table></figure></li><li>確認 path 有被修改到,在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$path</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># /opt/homebrew/bin /usr/local/bin ...</span></span><br></pre></td></tr></table></figure></li></ul></li></ol><h1 id="安裝-for-intel-x86-的-homebrew"><a href="#安裝-for-intel-x86-的-homebrew" class="headerlink" title="安裝 for intel(x86) 的 homebrew"></a>安裝 for intel(x86) 的 homebrew</h1><p>由於舊的套件可能不支援 m1,所以另外透過 <a href="https://support.apple.com/zh-tw/HT211861" target="_blank" rel="noopener">Rosetta</a> 安裝一個 for intel 版本的 homebrew</p><ol><li>在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 系統會將 homebrew 安裝在 /usr/local</span></span><br><span class="line">arch -x86_64 /bin/bash -c <span class="string">"<span class="variable">$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)</span>"</span></span><br></pre></td></tr></table></figure></li><li>在 terminal 執行以下指令,打開 .zshrc <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">open ~/.zshrc</span><br></pre></td></tr></table></figure><ul><li>將以下新增至 .zshrc 檔案中 <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 讓 ibrew 的指令會去找安裝在 /usr/local/ 的 homebrew</span><br><span class="line">alias ibrew='arch -x86_64 /usr/local/bin/brew'</span><br></pre></td></tr></table></figure></li></ul></li><li>確認有成功安裝<ul><li>在 terminal 執行 ibrew config 可正常執行 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ibrew config</span><br><span class="line"></span><br><span class="line"><span class="comment">## ...</span></span><br><span class="line"><span class="comment">## macOS: 11.4-x86_64</span></span><br><span class="line"><span class="comment">## ...</span></span><br><span class="line"><span class="comment">## Rosetta 2: true</span></span><br></pre></td></tr></table></figure></li></ul></li></ol><h1 id="安裝-oh-my-zsh"><a href="#安裝-oh-my-zsh" class="headerlink" title="安裝 oh my zsh"></a>安裝 <a href="https://github.com/ohmyzsh/ohmyzsh" target="_blank" rel="noopener">oh my zsh</a></h1><ol><li><p>在 terminal 執行以下指令</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c <span class="string">"<span class="variable">$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>"</span></span><br></pre></td></tr></table></figure><p> 完成的話就安裝好囉</p></li><li><p>安裝字型</p><ul><li>在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先執行這行,才能用 homebrew 安裝字型。</span></span><br><span class="line"><span class="comment"># 曾經執行過的人可以跳過這個指令</span></span><br><span class="line"><span class="comment"># 舊指令:brew tap caskroom/fonts</span></span><br><span class="line">brew tap homebrew/cask-fonts</span><br><span class="line"></span><br><span class="line"><span class="comment"># 看有哪些 nerd 字型可以下載</span></span><br><span class="line">brew search nerd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 挑一個字型來安裝</span></span><br><span class="line">brew install font-go-mono-nerd-font</span><br></pre></td></tr></table></figure></li><li>修改字型<ul><li>在 terminal 的 Preferences -> Profiles -> Text 修改(如圖圈起來的地方),選剛剛下載的字型<br><img src="https://i.imgur.com/7R6oaIW.png" alt=""></li></ul></li></ul></li><li><p>在 terminal 執行以下指令,安裝主題套件 <a href="https://github.com/Powerlevel9k/powerlevel9k" target="_blank" rel="noopener">powerlevel9k</a></p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/bhilburn/powerlevel9k.git ~/.oh-my-zsh/custom/themes/powerlevel9k</span><br></pre></td></tr></table></figure></li><li><p>編輯 ~/.zshrc 檔案 (大家可以依照自己的喜好設定)</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">ZSH_THEME="powerlevel9k/powerlevel9k"</span><br><span class="line"></span><br><span class="line">POWERLEVEL9K_MODE='nerdfont-complete'</span><br><span class="line"></span><br><span class="line">DEFAULT_USER="cindy"</span><br><span class="line"></span><br><span class="line"># command line 左邊想顯示的內容</span><br><span class="line">POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(context dir dir_writable vcs vi_mode)</span><br><span class="line"></span><br><span class="line"># command line 右邊想顯示的內容</span><br><span class="line">POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status ram load time)</span><br></pre></td></tr></table></figure></li><li><p>修改顏色</p><ul><li>在 terminal 的 Preferences > Profiles > Colors (如圖),選自己想要的顏色<br><img src="https://i.imgur.com/CjmzaDr.png" alt=""></li></ul></li><li><p>安裝 <a href="https://github.com/zsh-users/zsh-autosuggestions" target="_blank" rel="noopener">zsh-autosuggestions</a> 和 <a href="https://github.com/zsh-users/zsh-syntax-highlighting" target="_blank" rel="noopener">zsh-syntax-highlighting</a> 套件</p><ul><li>在 terminal 執行以下指令 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions</span><br><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure></li><li>修改 .zshrc 檔案 <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins=(git zsh-autosuggestions zsh-syntax-highlighting)</span><br></pre></td></tr></table></figure></li></ul></li><li><p>成果<br><img src="https://i.imgur.com/e9OeLRT.png" alt=""></p></li></ol><p>參考資料:</p><ul><li><a href="https://medium.com/statementdog-engineering/prettify-your-zsh-command-line-prompt-3ca2acc967f" target="_blank" rel="noopener">超簡單!十分鐘打造漂亮又好用的 zsh command line 環境</a></li><li><a href="https://medium.com/%E6%95%B8%E6%93%9A%E4%B8%8D%E6%AD%A2-not-only-data/macos-%E7%9A%84-terminal-%E5%A4%A7%E6%94%B9%E9%80%A0-iterms-oh-my-zsh-%E5%85%A8%E6%94%BB%E7%95%A5-77d5aae87b10" target="_blank" rel="noopener">如何讓 Terminal 看起來好用又好看|iTerms 2 + Oh-my-zsh 全攻略</a></li></ul><h1 id="設定-Sublime-Text"><a href="#設定-Sublime-Text" class="headerlink" title="設定 Sublime Text"></a>設定 Sublime Text</h1><ol><li><p>在 sublime 的 Preferences -> Settings 打開後右邊檔案加入個人設定</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"auto_complete"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"caret_extra_bottom"</span>: <span class="number">0</span>,</span><br><span class="line"> <span class="attr">"caret_extra_top"</span>: <span class="number">0</span>,</span><br><span class="line"> <span class="attr">"caret_extra_width"</span>: <span class="number">2</span>,</span><br><span class="line"> <span class="attr">"copy_with_empty_selection"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"ensure_newline_at_eof_on_save"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"font_face"</span>: <span class="string">"Roboto Mono for Powerline"</span>,</span><br><span class="line"> <span class="attr">"font_size"</span>: <span class="number">19</span>,</span><br><span class="line"> <span class="attr">"highlight_line"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"ignored_packages"</span>: [<span class="string">"Vintage"</span>],</span><br><span class="line"> <span class="attr">"index_files"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"overlay_scroll_bars"</span>: <span class="string">"enabled"</span>,</span><br><span class="line"> <span class="attr">"rulers"</span>: [<span class="number">79</span>],</span><br><span class="line"> <span class="attr">"save_on_focus_lost"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"spell_check"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"tab_size"</span>: <span class="number">2</span>,</span><br><span class="line"> <span class="attr">"theme"</span>: <span class="string">"auto"</span>,</span><br><span class="line"> <span class="attr">"translate_tabs_to_spaces"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"trim_trailing_white_space_on_save"</span>: <span class="string">"all"</span>,</span><br><span class="line"> <span class="attr">"color_scheme"</span>: <span class="string">"Packages/Color Scheme - Default/Monokai.sublime-color-scheme"</span>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>安裝套件</p><ul><li><a href="https://packagecontrol.io" target="_blank" rel="noopener">Package Control</a> 預設已安裝<ul><li>在 sublime 的 Preferences -> Package Control</li><li>點 Install Package 輸入以下要安裝的套件<ul><li><a href="https://github.com/skuroda/Sublime-AdvancedNewFile" target="_blank" rel="noopener">AdvancedNewFile</a></li><li><a href="https://github.com/seanliang/ConvertToUTF8" target="_blank" rel="noopener">ConvertToUTF8</a></li><li><a href="https://emmet.io/" target="_blank" rel="noopener">Emmet</a></li><li><a href="https://github.com/jisaacks/GitGutter" target="_blank" rel="noopener">GitGutter</a></li><li><a href="https://github.com/dzhibas/SublimePrettyJson" target="_blank" rel="noopener">Pretty Json</a></li><li><a href="https://github.com/titoBouzout/SideBarEnhancements" target="_blank" rel="noopener">SideBarEnhancements</a></li><li><a href="https://github.com/facelessuser/MarkdownPreview" target="_blank" rel="noopener">MarkdownPreview</a></li><li><a href="https://github.com/alepez/LiveReload-sublimetext3" target="_blank" rel="noopener">LiveReload</a></li></ul></li></ul></li></ul></li><li><p>在 terminal 執行以下指令,設定可直接在 terminal 用 subl 開啟 sublime</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 建立軟連結</span></span><br><span class="line">ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/<span class="built_in">local</span>/bin/subl</span><br></pre></td></tr></table></figure></li></ol><h1 id="安裝-rbenv"><a href="#安裝-rbenv" class="headerlink" title="安裝 rbenv"></a>安裝 <a href="https://github.com/rbenv/rbenv#choosing-the-ruby-version" target="_blank" rel="noopener">rbenv</a></h1><p>ruby 管理工具</p><ol><li><p>確認 <a href="https://formulae.brew.sh/formula/rbenv#default" target="_blank" rel="noopener">rbenv</a> 從 homebrew 安裝是否有支援 m1</p></li><li><p>在 terminal 執行以下指令</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install rbenv</span><br></pre></td></tr></table></figure></li><li><p>在 terminal 執行以下指令,會顯示要設定在 .zshrc 檔案中的指令</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rbenv init</span><br></pre></td></tr></table></figure></li><li><p>編輯 .zshrc 檔案,想知道詳細做了什麼可以參考 <a href="https://github.com/rbenv/rbenv#how-rbenv-hooks-into-your-shell" target="_blank" rel="noopener">How rbenv hooks into your shell</a>,由於每次打開 terminal 都會執行一次 ~/.zshrc 所以可以將 rbenv 的路徑放到 path 中 (<a href="https://github.com/rbenv/rbenv#understanding-shims" target="_blank" rel="noopener">Understanding Shims</a>)</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eval "$(rbenv init -)"</span><br></pre></td></tr></table></figure></li><li><p>在 terminal 開新分頁,執行以下指令確認有安裝成功</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 確認 rbenv 有被加到 path 中</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$path</span></span><br></pre></td></tr></table></figure></li><li><p>在 terminal 執行以下指令,安裝 ruby 3.0.1</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出最後穩定版本</span></span><br><span class="line">rbenv install -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有 local 版本</span></span><br><span class="line">rbenv install -L</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安裝 3.0.1</span></span><br><span class="line">rbenv install 3.0.1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 確認已安裝的 ruby 版本</span></span><br><span class="line">rbenv versions</span><br></pre></td></tr></table></figure><p> 參考資料: <a href="https://makandracards.com/makandra/21545-rbenv-how-to-switch-to-another-ruby-version-temporarily-per-project-or-globally" target="_blank" rel="noopener">rbenv: How to switch to another Ruby version (temporarily, per project, or globally)</a></p></li><li><p>在 terminal 執行以下指令,安裝 ruby 2.6.7</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CFLAGS="-Wno-error=implicit-function-declaration" rbenv install 2.6.7</span><br></pre></td></tr></table></figure><p> 遇到 <a href="https://github.com/rbenv/ruby-build/issues/1747" target="_blank" rel="noopener">Installing 2.6.7 on macOS 11.2.3 fails with implicit-function-declaration error</a> 問題</p></li><li><p>在 terminal 執行以下指令,安裝 ruby 2.3.7<br> 舊版的 ruby 在 arm64 下超級難裝,會遇到 <a href="https://github.com/rbenv/ruby-build/issues/1353" target="_blank" rel="noopener">openssl 要去抓舊版 1.0 但是已經抓不到的情況</a>,還會遇到 <a href="https://github.com/rbenv/ruby-build/issues/1691" target="_blank" rel="noopener">ffi 的問題</a>,所以這邊勇者做了各種嘗試 (不確定是否有可以省略的步驟),最後終於成功了!<br> 註:</p><ul><li>聽同事說他是完全在 x86_64 的環境下安裝成功的,所以我做了以下步驟</li><li>為了防止 arm64 版本安裝的套件被讀取到,所以我把 <code>export PATH=/opt/homebrew/bin:$PATH</code> 暫時先 hide 起來 <figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">ibrew install openssl@1.<span class="number">0</span></span><br><span class="line">ibrew install readline</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改用 x86_64 版的 homebrew 重新安裝 rbenv</span></span><br><span class="line">brew uninstall rbenv</span><br><span class="line">ibrew install rbenv</span><br><span class="line"></span><br><span class="line">brew uninstall ruby-build</span><br><span class="line">ibrew reinstall ruby-build</span><br><span class="line"></span><br><span class="line">export LDFLAGS=<span class="string">"-L/usr/local/opt/[email protected]/lib"</span></span><br><span class="line">export CPPFLAGS=<span class="string">"-I/usr/local/opt/[email protected]/include"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># RUBY_CFLAGS="-Wno-error=implicit-function-declaration" 解決 ffi 的問題</span></span><br><span class="line"><span class="comment"># CONFIGURE_OPTS="--with-openssl-dir=$(ibrew --prefix [email protected])" RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(ibrew --prefix [email protected])" 解決找不到 openssl 的問題</span></span><br><span class="line">RUBY_CFLAGS=<span class="string">"-Wno-error=implicit-function-declaration"</span> CONFIGURE_OPTS=<span class="string">"--with-openssl-dir=$(ibrew --prefix [email protected])"</span> RUBY_CONFIGURE_OPTS=<span class="string">"--with-openssl-dir=$(ibrew --prefix [email protected])"</span> arch -x86_64 rbenv install <span class="number">2.3</span>.<span class="number">7</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>安裝 bundler (如果沒有的話)</p> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gem install bundler</span><br></pre></td></tr></table></figure></li></ol><h1 id="安裝-nvm"><a href="#安裝-nvm" class="headerlink" title="安裝 nvm"></a>安裝 <a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener">nvm</a></h1><p>nvm 是管理 Node.js 的套件,類似 Ruby 的 rbenv 或 rvm</p><ol><li>輸入以下指令進行安裝 <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash</span><br></pre></td></tr></table></figure></li><li>將以下新增至 .zshrc 檔案中 <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">export NVM_DIR="$HOME/.nvm"</span><br><span class="line">[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm</span><br><span class="line">[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion</span><br></pre></td></tr></table></figure></li><li>安裝 Node.js<br> 安裝 v15 的時候會出現跑很久的情況,但等待之後會成功安裝,可參考<a href="https://github.com/nvm-sh/nvm/issues/2518" target="_blank" rel="noopener">這裡</a> <figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">nvm install v16</span><br><span class="line">nvm install v15</span><br><span class="line"></span><br><span class="line">nvm list</span><br></pre></td></tr></table></figure></li></ol><h1 id="安裝-yarn"><a href="#安裝-yarn" class="headerlink" title="安裝 yarn"></a>安裝 yarn</h1><figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install yarn</span><br></pre></td></tr></table></figure><h1 id="安裝-MySQL-amp-PostgreSQL-amp-Redis"><a href="#安裝-MySQL-amp-PostgreSQL-amp-Redis" class="headerlink" title="安裝 MySQL & PostgreSQL & Redis"></a>安裝 MySQL & PostgreSQL & Redis</h1><p>直接用 homebrew 裝就可以囉,經查詢 <a href="https://formulae.brew.sh/formula/mysql" target="_blank" rel="noopener">MySQL</a> 5.7 和 <a href="https://formulae.brew.sh/formula/postgresql" target="_blank" rel="noopener">PostgreSQL</a> 9.5 以上目前都有支援 Apple Silicon 了</p><figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">brew install [email protected]</span><br><span class="line">brew install mysql</span><br><span class="line"></span><br><span class="line">brew services start [email protected]</span><br><span class="line">brew services stop [email protected]</span><br><span class="line"></span><br><span class="line">brew install [email protected]</span><br><span class="line">brew install postgresql</span><br><span class="line"></span><br><span class="line">brew services start [email protected]</span><br><span class="line">brew services stop [email protected]</span><br><span class="line"></span><br><span class="line">brew install redis</span><br><span class="line">brew services start redis</span><br><span class="line">redis-cli</span><br><span class="line"></span><br><span class="line">brew services list</span><br></pre></td></tr></table></figure><h1 id="安裝-rails"><a href="#安裝-rails" class="headerlink" title="安裝 rails"></a>安裝 rails</h1><figure class="highlight zsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gem install rails</span><br></pre></td></tr></table></figure><h1 id="安裝其他套件"><a href="#安裝其他套件" class="headerlink" title="安裝其他套件"></a>安裝其他套件</h1><ul><li><a href="[tree](https://formulae.brew.sh/formula/tree)">tree</a></li><li><a href="https://formulae.brew.sh/formula/htop" target="_blank" rel="noopener">htop</a></li><li>安裝 <a href="https://github.com/deepfryed/idn-ruby" target="_blank" rel="noopener">idn-ruby</a> 遇到問題<ul><li><code>brew install libidn</code></li><li><code>gem install idn-ruby -v '0.1.0' -- --with-idn-dir=/opt/homebrew/Cellar/libidn/1.37</code></li></ul></li><li><a href="https://formulae.brew.sh/formula/imagemagick" target="_blank" rel="noopener">imagemagick</a></li><li><a href="https://formulae.brew.sh/formula/the_silver_searcher" target="_blank" rel="noopener">the_silver_searcher</a>:可使用 ag 進行搜尋</li></ul>]]></content>
<tags>
<tag> macbook </tag>
<tag> m1 </tag>
</tags>
</entry>
<entry>
<title>什麼是 Docker multi stage?</title>
<link href="/2021/05/23/What-is-docker-multi-stage/"/>
<url>/2021/05/23/What-is-docker-multi-stage/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,前一陣子因爲工作夥伴們遇到的問題,而研究了一下 <a href="https://docs.docker.com" target="_blank" rel="noopener">Docker</a> 的 multi-stage build,<br>故事是這樣的:</p><p>小夥伴們開發了一個 gem,push 到客戶的 GitLab,遇到了兩次 pipelines 失敗的情況:</p><ul><li>第一次,在 GitLab runner 開始進行 test stage 的 bundle install 的時候,因為沒有 pull GitLab repository 的權限,而無法下載客製化的 gem。<ul><li>解決方法:在 <code>.gitlab-ci.yml</code> 的 before_script 做 GitLab runner ssh 的設定,並將 <code>$SSH_PRIVATE_KEY</code> 放在 GitLab runner 的環境變數中,詳細可參考 <a href="https://docs.gitlab.com/ee/ci/ssh_keys" target="_blank" rel="noopener">Using SSH keys with GitLab CI/CD</a>。</li></ul></li><li>第二次,在 GitLab runner 要進行部署的時候,現況 GitLab runner 會進行 docker build 將 Rails 打包成 image 之後,再 push 到 <a href="https://aws.amazon.com/tw/ecr" target="_blank" rel="noopener">AWS 的 ECR</a>,接著會用 <a href="https://github.com/silinternational/ecs-deploy" target="_blank" rel="noopener">ecs-deploy</a> 的指令進行部署,將先前 push 到 ECR 上的 Rails image 部署到 <a href="https://aws.amazon.com/tw/ecs" target="_blank" rel="noopener">AWS 的 ECS</a>,這邊在進行 docker build 的時候,會在 Dockerfile 中執行 bundle install,因為 Dockerfile 沒有 pull GitLab repository 的權限,而無法下載客製化的 gem。<ul><li>解決方法:在 GitLab runner 執行 docker build 的時候,將 GitLab runner 的 <code>$SSH_PRIVATE_KEY</code> 當作參數傳進 Dockerfile 中。</li></ul></li></ul><p>到這邊大概是整個故事,但 docker multi stage 還沒出現噎,讓我娓娓道來,上面提到的 <strong>在 GitLab runner 執行 docker build 的時候,將 GitLab runner 的 <code>$SSH_PRIVATE_KEY</code> 當作參數傳進 Dockerfile 中。</strong> 這個解決方法有一個前提,就是我們必須確定 <code>$SSH_PRIVATE_KEY</code> 的傳遞是安全的!也就是說這把 key 不應該出現在 image 的任何記錄之中,否則只要拿到 image 就拿到 key 了。</p><h2 id="先說結論:用-multi-stage-build-可以解決-key-被記錄下來的問題,但-multi-stage-想要解決的主要問題其實是因為-build-image-太大。"><a href="#先說結論:用-multi-stage-build-可以解決-key-被記錄下來的問題,但-multi-stage-想要解決的主要問題其實是因為-build-image-太大。" class="headerlink" title="先說結論:用 multi-stage build 可以解決 key 被記錄下來的問題,但 multi-stage 想要解決的主要問題其實是因為 build image 太大。"></a>先說結論:用 multi-stage build 可以解決 key 被記錄下來的問題,但 multi-stage 想要解決的主要問題其實是因為 build image 太大。</h2><p>想知道為什麼就繼續往下看吧~</p><h2 id="Docker-image-layers"><a href="#Docker-image-layers" class="headerlink" title="Docker image layers"></a>Docker image layers</h2><p>先跟大家介紹個名詞,<strong><a href="https://docs.docker.com/storage/storagedriver/#images-and-layers" target="_blank" rel="noopener">Docker image layers</a></strong>,在 Dockerfile 中的每個指令會產生一個 image layer,且每層 image layer 會記錄比上一個 image layer 有哪些檔案的差異,層層堆疊上去,然後把檔案加總起來,我們可以從這些 layers 中查看 build image 的所有檔案紀錄。</p><p>接下來示範一下要怎麼看這些檔案裡面的全部內容,例如我已經有一個叫做 railsapp 的 image,執行以下步驟可觀察 image 的全貌:</p><ol><li><p>執行 <code>docker save -o railsapp.tar railsapp</code> 用 <a href="https://docs.docker.com/engine/reference/commandline/save" target="_blank" rel="noopener">docker save</a> 的指令將 image 存成一個壓縮檔。</p></li><li><p>執行 <code>tar -zxvf railsapp.tar</code> 進行解壓縮。</p><p><img src="https://i.imgur.com/5CMh3zG.png" alt=""></p></li><li><p>打開 manifest.json,到這邊就可以看到 image 有幾個 layer 了!</p><p><img src="https://i.imgur.com/bGYGJKE.png" alt=""></p></li><li><p>打開 Config a2a3bd208cd4fc2d2c847f2cc0cd241d6171c9b432db844f00bd47dc681df436.json,順道一提我們看到的這些檔案就是符合 <a href="https://github.com/opencontainers/image-spec/blob/master/spec.md#open-container-initiative" target="_blank" rel="noopener">OCI 的格式</a>(可以簡單理解成容器化技術開放出的一定格式),可以從這個 json 檔看到有很多的紀錄,包含了環境變數、執行過的 command 等等。</p></li><li><p><code>cd e14687248cea12d8112c3102c8d16604829553403933cda75612e49d5724d027</code> 進去其中一層 layer,然後 <code>tar -zxvf layer.tar</code> 解壓縮看看,結果如下:</p><p><img src="https://i.imgur.com/IUv5ONd.png" alt=""></p><p>可以看到這邊就是一些資料夾和檔案,也就是我們在執行 image 的時候裡面的資料夾和檔案。</p></li></ol><p>到這邊跟大家說明了,docker image 裡面包含的東西,接下來才是這篇文章的主角(<del>鋪成也太長</del>)。</p><h2 id="multi-stage-builds"><a href="#multi-stage-builds" class="headerlink" title="multi-stage builds"></a>multi-stage builds</h2><p>在 Docker 的文件中的 <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds" target="_blank" rel="noopener">Best practices for writing Dockerfiles</a> 這篇文章就有建議大家使用 multi-stage builds,那麼究竟什麼是 multi-stage builds 呢? <a href="https://docs.docker.com/develop/develop-images/multistage-build" target="_blank" rel="noopener">Use multi-stage builds</a> 的文章中提到,在 multi-stage 之前,如果我們有需要兩個 image 的情況,可能會需要寫一個 script 先 build 第一個 image,產生一個 container 將需要的資料夾或檔案複製出來,然後再 build 第二個 image,最後再刪掉中間使用到的資料夾或檔案。</p><p>multi-stage 解決了上述麻煩的步驟,讓我們可以在同一個 Dockerfile 撰寫數個中間會用到的 image,而不會被保存下來,文件的範例如下:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:1</span></span><br><span class="line"><span class="keyword">FROM</span> golang:<span class="number">1.16</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /go/src/github.com/alexellis/href-counter/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> go get -d -v golang.org/x/net/html</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> app.go .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> alpine:latest</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apk --no-cache add ca-certificates</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /root/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> --from=0 /go/src/github.com/alexellis/href-counter/app .</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"./app"</span>]</span></span><br></pre></td></tr></table></figure><p>第 2 到 6 行其實就是第一個要 build 的 image,而第 8 到 12 行就是第二個要 build 的 image,而 <code>COPY --from=0 /go/src/github.com/alexellis/href-counter/app .</code> 就是將我們需要的第一個 image 的資料夾複製到第二個 image 之中,而第一個 image 因為是中間使用到的 image,所以並不會在最後生成的 image layers 之中!</p><p>大家發現了嗎?先前有提到 image layers 中有各種紀錄,而 multi-stage 中的中間 image 不會產生 layer 也就是不會有紀錄的意思囉!所以我們可以用 multi-stage 的方式在中間的 image 拿到 key 之後進行 bundle install,而將需要的檔案複製到最後產生的 image 之中,就不用擔心 image 裡會有 key 了!就像先前提到的,這並不是 multi-stage 想要解決的問題,主要是因為少了這些中間 image layers 也就意味著 image 的 size 是會變小的!</p><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><ul><li><p>小技巧:利用 multi-stage 可以傳遞 key 而不會被記錄在 image 紀錄之中,善用 multi-stage 可以大幅降低 image 的 size。</p></li><li><p>其他 Docker 傳遞私密資訊的方式:</p><ul><li><p><a href="https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information" target="_blank" rel="noopener"><code>--secret</code></a><br>在新版 Docker Engine API (v1.39+) 中,可將外部檔案的 secret 帶進 Dockerfile 之中而不會被存在紀錄之中,但前提是要用 <a href="https://docs.docker.com/develop/develop-images/build_enhancements" target="_blank" rel="noopener"><strong>BuildKit</strong></a> 才可以使用。使用方式如下:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:1.0.0-experimental</span></span><br><span class="line"><span class="keyword">FROM</span> alpine</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> --mount=<span class="built_in">type</span>=secret,id=mysite.key <span class="built_in">command</span>-to-run</span></span><br></pre></td></tr></table></figure><p><code>docker build --secret id=mysite.key,src=path/to/mysite.key .</code></p></li><li><p><a href="https://docs.docker.com/develop/develop-images/build_enhancements/#using-ssh-to-access-private-data-in-builds" target="_blank" rel="noopener"><code>--ssh</code></a><br>在新版 Docker Engine API (v1.39+) 中,可以將現有的 SSH agent connection 或 key 傳遞進 Dockerfile,跟上面一樣要是 <a href="https://docs.docker.com/develop/develop-images/build_enhancements" target="_blank" rel="noopener"><strong>BuildKit</strong></a> 才可以用。使用方式如下:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:experimental</span></span><br><span class="line"><span class="keyword">FROM</span> alpine</span><br><span class="line"><span class="comment"># install ssh client and git</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apk add --no-cache openssh-client git</span></span><br><span class="line"><span class="comment"># download public key for github.com</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts</span></span><br><span class="line"><span class="comment"># clone our private repository</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> --mount=<span class="built_in">type</span>=ssh git <span class="built_in">clone</span> [email protected]:myorg/myproject.git myproject</span></span><br></pre></td></tr></table></figure><p><code>docker build --ssh default .</code></p></li></ul><p>詳細可參考:</p><ul><li><a href="https://medium.com/@tonistiigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066" target="_blank" rel="noopener">Build secrets and SSH forwarding in Docker 18.09</a></li><li><a href="https://blog.wu-boy.com/2020/04/speed-up-docker-build-using-docker-buildkit" target="_blank" rel="noopener">使用 Docker BuildKit 加速編譯 Image</a></li></ul></li></ul>]]></content>
<tags>
<tag> Docker </tag>
<tag> docker multi stage </tag>
<tag> docker layer </tag>
</tags>
</entry>
<entry>
<title>使用 Burp Suite 模擬駭客進行 DoS 攻擊</title>
<link href="/2021/05/07/Burp-Suite-DoS-attack/"/>
<url>/2021/05/07/Burp-Suite-DoS-attack/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近因為工作的關係知道了 <a href="https://portswigger.net/burp" target="_blank" rel="noopener">Burp Suite</a> 這套軟體,主要是可以對網站進行安全性測試的軟體,因為可以免費試用,所以當然要下載來用看看呀!(本篇不會介紹怎麼下載,因為跟著官網的步驟就可以完成囉!)</p><p>在開始模擬駭客之前,先幫大家科普一下什麼是 <a href="https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A" target="_blank" rel="noopener">DoS 攻擊</a>?</p><ul><li>阻斷服務攻擊(英語:denial-of-service attack,簡稱 DoS 攻擊)亦稱洪水攻擊,是一種網路攻擊手法,其目的在於使目標電腦的網路或系統資源耗盡,使服務暫時中斷或停止,導致其正常使用者無法存取。</li></ul><p>其實維基百科就寫得很清楚了 XD</p><p>那麼知道 DoS 是什麼之後我們就要開始模擬駭客了,首先我的目標是要對我的網站進行攻擊,網站有忘記密碼的功能,在使用者輸入 email 之後會寄信給使用者,這邊先攔截第一次的 request,接著發送多筆相同的 request 進行攻擊,目的是讓使用者收到大量相同的信件,消耗攻擊目標信件的資源。</p><p>在開始之前先把要攻擊的網站做出來,最近發現工作久了實在是不常做 rails new,這樣好像不太好,趁現在來練一下?<br>大家可以用這個 <a href="https://github.com/cindyliu923/test_dos_attack" target="_blank" rel="noopener">測試用專案</a> 在本地端進行測試,因為已經在開發環境安裝 <a href="https://github.com/ryanb/letter_opener" target="_blank" rel="noopener">letter_opener</a> 這個套件,所以可以直接看到發送了幾封信。</p><h2 id="開始測試"><a href="#開始測試" class="headerlink" title="開始測試"></a>開始測試</h2><ol><li>打開 Burp Suite 的 Proxy 的 Intercept,點選 Open Browser</li><li>在 Browser 輸入要攻擊網站的網址,這邊要測試本地端的忘記密碼頁面,所以是 <code>http://localhost:3000/users/password/new</code></li><li>點選 Intercept is off,此按鈕會變藍色並顯示 Intercept is on 的文字,表示接下來在 Browser 的 request 會先被攔截下來,Burp Suite 和 Browser 的畫面如下:<br><img src="https://i.imgur.com/OWDNW9P.png" alt=""></li><li>點選 Send me reset password instructions 的按鈕,request 會被攔截,將 Host 反色後點選滑鼠右鍵 Send to Intruder,如下圖:<br><img src="https://i.imgur.com/hsCekas.png" alt=""></li><li>可以在 Intruder 的 Position 設定 payload 的範圍,接著到 Payloads 設定,這邊的設定會產生 9 個 request,畫面如下:<br><img src="https://i.imgur.com/tZzdNXB.png" alt=""></li></ol><ul><li>註:在 Position 設定中用符號 § 標記範圍</li></ul><ol start="6"><li>點選 Start attack,畫面上 302 有轉址的表示有成功寄出信件(這邊因為沒有調整 Position 所以有些是失敗的)<br><img src="https://i.imgur.com/OEilxGe.png" alt=""></li><li>最後回到 Proxy 點選 Intercept is on 的按鈕,讓攔截停止,這樣就完成了一次的攻擊了。</li></ol><p>用上面的方法就可以對自己的網站進行測試囉!</p><h2 id="延伸閱讀"><a href="#延伸閱讀" class="headerlink" title="延伸閱讀"></a>延伸閱讀</h2><ul><li><a href="https://www.choice-design.com.tw/article_detail99.html" target="_blank" rel="noopener">為什麼我的網站會被DDOS</a></li><li><a href="https://www.hackercat.org/burp-suite-tutorial/web-pentesting-burp-suite-total-tutorial" target="_blank" rel="noopener">Web滲透測試 – Burp Suite 完整教學系列</a></li><li><a href="https://medium.com/starbugs/%E8%BA%AB%E7%82%BA-web-%E5%B7%A5%E7%A8%8B%E5%B8%AB-%E4%BD%A0%E4%B8%80%E5%AE%9A%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E5%B9%BE%E5%80%8B-web-%E8%B3%87%E8%A8%8A%E5%AE%89%E5%85%A8%E8%AD%B0%E9%A1%8C-29b8a4af6e13" target="_blank" rel="noopener">身為 Web 工程師,你一定要知道的幾個 Web 資訊安全議題</a></li></ul>]]></content>
<tags>
<tag> Burp Suite </tag>
<tag> DoS </tag>
<tag> security </tag>
</tags>
</entry>
<entry>
<title>CS50 week 6 - Python 筆記</title>
<link href="/2021/02/13/cs50-week-6-notes/"/>
<url>/2021/02/13/cs50-week-6-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=kM4oZTJaO8k&start=624" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 6 筆記,想先看之前筆記的人可以點選下面連結:</p><ul><li><a href="/2020/09/12/cs50-week-0-notes">week 0</a></li><li><a href="/2020/09/19/cs50-week-1-notes">week 1</a></li><li><a href="/2020/09/27/cs50-week-2-notes">week 2</a></li><li><a href="/2020/10/10/cs50-week-3-notes">week 3</a></li><li><a href="/2021/01/10/cs50-week-4-notes">week 4</a></li><li><a href="/2021/01/12/cs50-week-5-notes">week 5</a></li></ul><p>這堂課主要在教 Python 的語法,因為感覺 Python 跟 Ruby 很像,所以這篇文章我想讓這兩個語言的語法進行比較。</p><h2 id="hello-py"><a href="#hello-py" class="headerlink" title="hello.py"></a>hello.py</h2><p>學任何語言,首先一定要先打招呼XD</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># python</span></span><br><span class="line">print(<span class="string">"hello, world"</span>)</span><br></pre></td></tr></table></figure><p>這裡語法跟 Ruby 一樣,不過 Ruby 可以省略括號,所以可以寫這樣:</p><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line">print <span class="string">"hello, world"</span></span><br></pre></td></tr></table></figure><p>但是因為 Ruby 的 print 不會換行,所以感覺還是有點不一樣,這裡如果改用 <code>puts</code> 的話感覺比較像:</p><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line">puts <span class="string">"hello, world"</span></span><br></pre></td></tr></table></figure><ul><li>想了解 Ruby print、puts、p 及其他基礎語法的可以點 <a href="https://railsbook.tw/chapters/05-ruby-basic-1.html" target="_blank" rel="noopener">變數、常數、流程控制、迴圈</a></li></ul><h2 id="Variables"><a href="#Variables" class="headerlink" title="Variables"></a>Variables</h2><p>Python 跟 C 不一樣的地方在於,定義一個變數的時候不需要先宣告變數的型別是什麼,這點跟 Ruby 一樣。</p><h2 id="F-strings"><a href="#F-strings" class="headerlink" title="F-strings"></a>F-strings</h2><p>如果我們想在 String 裡放變數,可以這樣寫:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># python</span></span><br><span class="line">print(<span class="string">"hello, "</span> + answer)</span><br><span class="line"><span class="comment"># f + "" 告訴 python 要 format string,而不是直接將 {answer} 印出</span></span><br><span class="line">print(<span class="string">f"hello, <span class="subst">{answer}</span>"</span>)</span><br></pre></td></tr></table></figure><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line">puts <span class="string">"hello, "</span> + answer</span><br><span class="line">puts <span class="string">"hello, <span class="subst">#{answer}</span>"</span></span><br><span class="line"><span class="comment"># 下面這個方式跟 C 的寫法很像</span></span><br><span class="line">puts <span class="string">"hello, %s"</span> % answer</span><br></pre></td></tr></table></figure><ul><li><a href="https://www.rubyguides.com/2012/01/ruby-string-formatting/" target="_blank" rel="noopener">Ruby String Formatting</a></li></ul><h2 id="Conditions"><a href="#Conditions" class="headerlink" title="Conditions"></a>Conditions</h2><p>比較不一樣的是 Python 的 else if 是 <code>elif</code>,而 ruby 的是 <code>elsif</code></p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">if</span> x < y:</span><br><span class="line"> print(<span class="string">"x is less than y"</span>)</span><br><span class="line"><span class="keyword">elif</span> x > y:</span><br><span class="line"> print(<span class="string">"x is greater than y"</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"> print(<span class="string">"x is equal to y"</span>)</span><br></pre></td></tr></table></figure><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line"><span class="keyword">if</span> x < y</span><br><span class="line"> puts <span class="string">"x is less than y"</span></span><br><span class="line"><span class="keyword">elsif</span> x > y</span><br><span class="line"> puts <span class="string">"x is greater than y"</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> puts <span class="string">"x is equal to y"</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><h2 id="Loops"><a href="#Loops" class="headerlink" title="Loops"></a>Loops</h2><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> print(<span class="string">"hello, world"</span>)</span><br><span class="line"></span><br><span class="line">i = <span class="number">0</span></span><br><span class="line"><span class="keyword">while</span> i < <span class="number">3</span>:</span><br><span class="line"> print(<span class="string">"hello, world"</span>)</span><br><span class="line"> i += <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]:</span><br><span class="line"> print(<span class="string">"cough"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>):</span><br><span class="line"> print(<span class="string">"cough"</span>)</span><br></pre></td></tr></table></figure><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">true</span></span><br><span class="line"> puts <span class="string">"hello, world"</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">i = <span class="number">0</span></span><br><span class="line"><span class="keyword">while</span> i < <span class="number">3</span></span><br><span class="line"> puts <span class="string">"hello, world"</span></span><br><span class="line"> i += <span class="number">1</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]</span><br><span class="line"> puts <span class="string">"cough"</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span>..<span class="number">2</span></span><br><span class="line"> puts <span class="string">"cough"</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><h2 id="Library"><a href="#Library" class="headerlink" title="Library"></a>Library</h2><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># python</span></span><br><span class="line"><span class="comment"># 引用 Library 的特定方法</span></span><br><span class="line"><span class="keyword">from</span> cs50 <span class="keyword">import</span> get_float</span><br><span class="line"><span class="keyword">from</span> cs50 <span class="keyword">import</span> get_int</span><br><span class="line"><span class="keyword">from</span> cs50 <span class="keyword">import</span> get_string</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cs50</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> cs50 <span class="keyword">import</span> get_float, get_int, get_string</span><br></pre></td></tr></table></figure><p>在 Ruby 可以用 require 來引入 Library</p><figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ruby</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'rake'</span></span><br></pre></td></tr></table></figure><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>這篇文章放了好久都沒送出XD<br>這堂課接下來教授示範了 python 各種實用的用法,例如有做影像處理的 <a href="https://github.com/python-pillow/Pillow" target="_blank" rel="noopener">Pillow</a>,Text to Speech 的 <a href="https://github.com/nateshmbhat/pyttsx3" target="_blank" rel="noopener">pyttsx3</a>,<a href="https://github.com/ageitgey/face_recognition" target="_blank" rel="noopener">face_recognition</a>,<a href="https://github.com/Uberi/speech_recognition" target="_blank" rel="noopener">speech_recognition</a> 等等,很多實用的工具可以拿來玩看看囉。</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 5 - Data Structures 筆記</title>
<link href="/2021/01/12/cs50-week-5-notes/"/>
<url>/2021/01/12/cs50-week-5-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?t=671&v=r15JIzFHbbM&feature=youtu.be" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 5 筆記,想先看之前筆記的人可以點選下面連結:</p><ul><li><a href="/2020/09/12/cs50-week-0-notes">week 0</a></li><li><a href="/2020/09/19/cs50-week-1-notes">week 1</a></li><li><a href="/2020/09/27/cs50-week-2-notes">week 2</a></li><li><a href="/2020/10/10/cs50-week-3-notes">week 3</a></li><li><a href="/2021/01/10/cs50-week-4-notes">week 4</a></li></ul><h1 id="Data-Structures"><a href="#Data-Structures" class="headerlink" title="Data Structures"></a>Data Structures</h1><h2 id="Arrays"><a href="#Arrays" class="headerlink" title="Arrays"></a>Arrays</h2><p>Array 在記憶體中是連續存在的,當我們要在 array 新增一個值,本來儲存的記憶體位置可能會因為空間不夠而不能繼續存下去,這時候我們就會需要去換記憶體位子,將原本的 array 複製到記憶體的另一個長度夠的位子並插入新值,會<strong>花時間</strong>。</p><ul><li><code>realloc</code> 可以改變已配置的記憶體大小。</li></ul><h2 id="Linked-Lists"><a href="#Linked-Lists" class="headerlink" title="Linked Lists"></a>Linked Lists</h2><p>Linked Lists 在記憶體中是不連續,利用紀錄下一個 address 的方式連接,因為需要紀錄下一個 node 的 address,所以會<strong>花空間</strong>。</p><p>資料結構如下,每個 node 除了要記錄的值以外,還要記錄下一個 node 的 address。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">node</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> <span class="keyword">int</span> number;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">next</span>;</span></span><br><span class="line">}</span><br><span class="line">node;</span><br></pre></td></tr></table></figure><p>產生一個 node 的範例如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">node *n = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(node));</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (n != <span class="literal">NULL</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 這和 (*n).number = 1; 是一樣意思</span></span><br><span class="line"> n->number = <span class="number">1</span>;</span><br><span class="line"> n->next = <span class="literal">NULL</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>當我們想要在 linked list 裡插入另一個值時,操作的順序相當重要,必須讓要插入的值先指向 next 的 address,接著再讓前一個 node 的 next 改為新插入的值,範例如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假設我們已經有 linked list(變數名稱叫做 list),我們想要在 list 的最前面插入一個 node(變數名稱為 n)</span></span><br><span class="line">n->next = <span class="built_in">list</span>;</span><br><span class="line"><span class="built_in">list</span> = n;</span><br></pre></td></tr></table></figure><h2 id="Binary-Search-Trees"><a href="#Binary-Search-Trees" class="headerlink" title="Binary Search Trees"></a>Binary Search Trees</h2><pre><code> 4 / \ 2 6 / \ / \1 3 5 7</code></pre><p>我們可以用更抽象的樹狀結構的方式表示 Binary Search,每個節點的左邊都會小於節點,右邊會大於節點。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">node</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> <span class="keyword">int</span> number;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">left</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">right</span>;</span></span><br><span class="line">}</span><br><span class="line">node;</span><br></pre></td></tr></table></figure><h2 id="Hash-Tables"><a href="#Hash-Tables" class="headerlink" title="Hash Tables"></a>Hash Tables</h2><p>是 linked list 和 array 的結合,我們透過 <strong>Hash function</strong> 實現,最後可以讓我們用 input 找到特定的 output,只需要花費時間幾乎恆定的 O(1),在設計程式的時候要注意 Collision 的問題。</p><p>我們在 Hash 做搜尋的時候,最糟糕的情況會花費 O(n),例如我們先用分類的方式將資料放在不同的 bucket 裡,每個 bucket 的搜尋時間都會是 O(n)。</p><p>其他參考資料:</p><ul><li><a href="http://alrightchiu.github.io/SecondRound/hash-tableintrojian-jie.html" target="_blank" rel="noopener">Hash Table:Intro(簡介)</a></li><li><a href="https://medium.com/@lukabaramishvili/data-structure-hash-table-big-o-notation-a2ee869be861" target="_blank" rel="noopener">Data Structure: Hash Table & Big O Notation</a></li></ul><h2 id="Tries"><a href="#Tries" class="headerlink" title="Tries"></a>Tries</h2><p>可以想成是一種樹狀結構,每個 node 都是一個 array 且指向下一個 node。搜尋或插入都會是 O(1) 一個常數的 Step,但這個做法會耗費大量的記憶體空間。</p><h2 id="Queues"><a href="#Queues" class="headerlink" title="Queues"></a>Queues</h2><p>抽象的資料結構,First in first out(FIFO) 稱為 queue,<code>enqueue</code> 表示進入排隊的隊伍,<code>dequeue</code>表示已處理完離開排隊的隊伍。</p><h2 id="Stacks"><a href="#Stacks" class="headerlink" title="Stacks"></a>Stacks</h2><p>抽象的資料結構,Last in first out(LIFO) 稱為 stack,<code>push</code> 表示將值加入 stack,<code>pop</code> 表示從 stack 將值取出。</p><h2 id="Dictionaries"><a href="#Dictionaries" class="headerlink" title="Dictionaries"></a>Dictionaries</h2><p>抽象的資料結構,就像字典一樣可以透過 key 找到 value。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>這堂課教授說的一句話我覺得很好,就把它記下來分享給大家,<strong>Nothing is absolutely better than anything else</strong>,課程中教授不停的問學生這個資料結構付出的代價是什麼,大多數來說會是時間和空間的權衡,沒有任何一個完美的做法,只有當下最適合的選擇。</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 4 - Memory 筆記</title>
<link href="/2021/01/10/cs50-week-4-notes/"/>
<url>/2021/01/10/cs50-week-4-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?t=531&v=pcbmiLUzr0w&feature=youtu.be" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 4 筆記,想先看之前筆記的人可以點選下面連結:</p><ul><li><a href="/2020/09/12/cs50-week-0-notes">week 0</a></li><li><a href="/2020/09/19/cs50-week-1-notes">week 1</a></li><li><a href="/2020/09/27/cs50-week-2-notes">week 2</a></li><li><a href="/2020/10/10/cs50-week-3-notes">week 3</a></li></ul><h2 id="十六進位-Hexadecimal"><a href="#十六進位-Hexadecimal" class="headerlink" title="十六進位(Hexadecimal)"></a>十六進位(Hexadecimal)</h2><p>用 1 2 3 4 5 6 7 8 9 A B C D E F 表示每個數字,例如十進位的 10 改成用十六進位表示的話就是 A。</p><h2 id="RGB"><a href="#RGB" class="headerlink" title="RGB"></a>RGB</h2><p>紅綠藍三種顏色用十六進位表示,就是我們常看到的 <code>#FF0000</code> <code>#00FF00</code> <code>#0000FF</code>。</p><h2 id="記憶體位址"><a href="#記憶體位址" class="headerlink" title="記憶體位址"></a>記憶體位址</h2><p><code>int n = 50;</code> 當我們定義一個變數並給值,這些會被存在記憶體的位址中。</p><p>c 語言中可以看見變數的 addresses 是什麼:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">50</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%p\n"</span>, &n);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0x7ffd80792f7c (0x 前綴用來表示是 16 進位)</span></span><br></pre></td></tr></table></figure><p><code>&</code> 表示變數 n 的 address 是什麼,<code>%p</code> 表示 address 的 format (pointer)。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">50</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%i\n"</span>, *&n);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 50</span></span><br></pre></td></tr></table></figure><p><code>*</code> 表示進去 address 裡面看。</p><h2 id="Pointers"><a href="#Pointers" class="headerlink" title="Pointers"></a>Pointers</h2><p>在記憶體中儲存著變數的 address 就叫做 pointer,可以當作是指向記憶體變數的指標。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">50</span>;</span><br><span class="line"> <span class="keyword">int</span> *p = &n;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%p\n"</span>, p);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0x12345678</span></span><br></pre></td></tr></table></figure><p>將 address 儲存至變數時,前面要加一個 * 表示我知道自己在做什麼。<br>Pointer 往往佔了 8 bytes。</p><h2 id="Strings"><a href="#Strings" class="headerlink" title="Strings"></a>Strings</h2><p>存在記憶體中連續的位址,用 <code>\0</code> 結尾,pointer 表示的是 string 第一個字母在記憶體的位址。<br><code>Char *</code> 給你一個 pointer 指向字串中的第一個 character,其實就是 cs50 library 中的 string data type,而在 C 語言中會用 <code>Char *</code> 來表示。</p><ul><li><a href="https://zh.wikipedia.org/wiki/%E8%A8%98%E6%86%B6%E9%AB%94%E5%8D%80%E6%AE%B5%E9%8C%AF%E8%AA%A4" target="_blank" rel="noopener">Segmentation fault</a>: 當我們觸碰了不該觸碰的記憶體區域,會發生的錯誤。</li></ul><h3 id="compare"><a href="#compare" class="headerlink" title="compare"></a>compare</h3><p><code>char *</code> 定義的變數進行比較時,比較的會是 address 而不是字母本身,可以用 <code>strcmp()</code>(來自 string.h 的方法)進行比較。</p><h3 id="copy"><a href="#copy" class="headerlink" title="copy"></a>copy</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><ctype.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span> *s = get_string(<span class="string">"s: "</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span> *t = s;</span><br><span class="line"></span><br><span class="line"> t[<span class="number">0</span>] = <span class="built_in">toupper</span>(t[<span class="number">0</span>]);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"s: %s\n"</span>, s);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"t: %s\n"</span>, t);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面這段程式碼因為複製的是指標位址,所以結果會將 s 和 t 都一起改變(因為指向同一個位址)。類似我之前寫過的<a href="/2020/06/25/ruby-pass-by-reference-and-value">這篇</a>文章。</p><h3 id="malloc-amp-free"><a href="#malloc-amp-free" class="headerlink" title="malloc & free"></a>malloc & free</h3><p>上一個問題可以用下面的程式碼解決,其中 <code>malloc</code> 是當我們直接跟 c 語言要記憶體空間時可以使用(分配記憶體空間),<code>t == NULL</code> 表示記憶體空間 address 不存在的錯誤,最後 <code>free</code> 將先前用 <code>malloc</code> 要的記憶體空間釋出。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><ctype.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span> *s = get_string(<span class="string">"s: "</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span> *t = <span class="built_in">malloc</span>(<span class="built_in">strlen</span>(s) + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">if</span> (t == <span class="literal">NULL</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, n = <span class="built_in">strlen</span>(s); i < n + <span class="number">1</span>; i++)</span><br><span class="line"> {</span><br><span class="line"> t[i] = s[i];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strlen</span>(t) > <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> t[<span class="number">0</span>] = <span class="built_in">toupper</span>(t[<span class="number">0</span>]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"s: %s\n"</span>, s);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"t: %s\n"</span>, t);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">free</span>(t);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="valgrind"><a href="#valgrind" class="headerlink" title="valgrind"></a>valgrind</h2><p>valgrind 是一個協助我們找出關於記憶體錯誤的工具,例如下面這段程式碼,很明顯我們使用了 malloc 要記憶體空間卻沒有使用 free 釋放記憶體空間,且我們只跟記憶體要了 3 byte 的空間,卻使用了 4 bytes,當我們執行 <code>valgrind ./memory</code> 可以看到這些錯誤的提示訊息。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// memory.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span> *s = <span class="built_in">malloc</span>(<span class="number">3</span>); <span class="comment">// 應該改成 char *s = malloc(4);</span></span><br><span class="line"> s[<span class="number">0</span>] = <span class="string">'H'</span>;</span><br><span class="line"> s[<span class="number">1</span>] = <span class="string">'I'</span>;</span><br><span class="line"> s[<span class="number">2</span>] = <span class="string">'!'</span>;</span><br><span class="line"> s[<span class="number">3</span>] = <span class="string">'\0'</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s\n"</span>, s);</span><br><span class="line"> <span class="comment">// 應該加上 free(s);</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Garbage-values"><a href="#Garbage-values" class="headerlink" title="Garbage values"></a>Garbage values</h2><p>在我們將變數放進記憶體前,我們都不應該相信記憶體中已存在的東西,因為記憶體空間會被重複使用,曾經使用過記憶體空間不會被 reset,所以會存有所謂的 <strong>Garbage values</strong>,如果我們嘗試 dereference 還沒有被初始化過的 value,程式可能會發生 Segmentation fault,即我們觸碰了不該觸碰的記憶體空間。</p><p>這邊讓我想到 Ruby Conf Taiwan 2019 的時候 Aaron Patterson 大大演講的 <a href="https://www.youtube.com/watch?v=0ypPiULlKfQ&list=PLhKZ4RWngmpBgD6t068O_EZ4grSlUtl3Y&index=3" target="_blank" rel="noopener">Compacting GC for MRI</a>,順便懷念一下我菜逼巴的<a href="https://cindyliu923.medium.com/ruby-conf-taiwan-2019-d49c2add4ba5" target="_blank" rel="noopener">心得</a>。</p><h2 id="swap"><a href="#swap" class="headerlink" title="swap"></a>swap</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> x = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> y = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x is %i, y is %i\n"</span>, x, y);</span><br><span class="line"> swap(x, y);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x is %i, y is %i\n"</span>, x, y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> tmp = a;</span><br><span class="line"> a = b;</span><br><span class="line"> b = tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面這段程式碼實際上不會交換 x 和 y,原因如下:</p><h3 id="Memory-Layout"><a href="#Memory-Layout" class="headerlink" title="Memory Layout"></a>Memory Layout</h3><p>記憶體的空間配置如下:</p><ul><li>machine code</li><li>globals</li><li>heap 往下 (每當我們使用 malloc 會從這個區域分配空間)<br> |</li><li>stack 往上 (當我們 call function 的時候,使用的是這個區域的空間)</li></ul><p>swap function 和 main function 會分別存在 stack 中的不同位置,當 swap 執行完畢時,交換的是 a 和 b,而不是 x 和 y,即 a 和 b 只是 x 和 y 複製而成的值,所以我們改成將 x y 的位置傳進去 swap,修改後的程式碼如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> *a, <span class="keyword">int</span> *b)</span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> x = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> y = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x is %i, y is %i\n"</span>, x, y);</span><br><span class="line"> swap(&x, &y);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x is %i, y is %i\n"</span>, x, y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> *a, <span class="keyword">int</span> *b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> tmp = *a;</span><br><span class="line"> *a = *b;</span><br><span class="line"> *b = tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Heap-overflow-amp-Stack-overflow"><a href="#Heap-overflow-amp-Stack-overflow" class="headerlink" title="Heap overflow & Stack overflow"></a>Heap overflow & Stack overflow</h3><p>當 call function 的時候在記憶中 stack 的區域不停的往上使用空間,直到碰到了正在使用的 heap 區域,就被稱為 <strong>Stack overflow</strong>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">draw</span><span class="params">(<span class="keyword">int</span> h)</span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> <span class="built_in">height</span> = get_int(<span class="string">"Height: "</span>);</span><br><span class="line"> draw(<span class="built_in">height</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">draw</span><span class="params">(<span class="keyword">int</span> h)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> draw(h - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < h; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"#"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>當我們使用 recursion 時要特別注意,因為當我們不停的 call 自己的 function 時,而沒有給他停下來條件的話,程式會不停的往上使用 stack 的空間,最後出現 Segmentation fault,所以要將上面的程式碼改成下面這段程式碼:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">draw</span><span class="params">(<span class="keyword">int</span> h)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (h == <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> draw(h - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < h; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"#"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也就是說迭代(Iteration)可以確定不會發生 Stack overflow 的問題,但遞歸(Recursion)就要特別小心這個問題。</p><h3 id="Buffer-Overflow"><a href="#Buffer-Overflow" class="headerlink" title="Buffer Overflow"></a>Buffer Overflow</h3><p>表示我們使用了超過 Buffer 的記憶體空間。</p><h2 id="scanf"><a href="#scanf" class="headerlink" title="scanf"></a>scanf</h2><p>當我們想要使用用戶輸入的值,可以使用 scanf,範例如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> x;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x: "</span>);</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%i"</span>, &x);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"x: %i\n"</span>, x);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span> s[<span class="number">4</span>];</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"s: "</span>);</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%s"</span>, s);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"s: %s\n"</span>, s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="File-I-O"><a href="#File-I-O" class="headerlink" title="File I/O"></a>File I/O</h2><p>範例如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> FILE *file = fopen(<span class="string">"phonebook.csv"</span>, <span class="string">"a"</span>);</span><br><span class="line"> <span class="keyword">if</span> (file == <span class="literal">NULL</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span> *name = get_string(<span class="string">"Name: "</span>);</span><br><span class="line"> <span class="keyword">char</span> *number = get_string(<span class="string">"Number: "</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">fprintf</span>(file, <span class="string">"%s,%s\n"</span>, name, number);</span><br><span class="line"></span><br><span class="line"> fclose(file);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>特殊檔案例如 jpeg 有特殊的開頭(BYTE),可以查文件。</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 Lab 2: Scrabble</title>
<link href="/2020/12/07/CS50-Lab-2-Scrabble/"/>
<url>/2020/12/07/CS50-Lab-2-Scrabble/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=Tpl7k8IOT6E" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這篇文章是我練習寫 Week 2 的作業(因為不知道要放在哪裡,才不會以後找不到,所以就決定放在部落格啦),歡迎大家有更好的解法可以一起討論唷~</p><p>題目:<a href="https://cs50.harvard.edu/college/2020/fall/labs/2" target="_blank" rel="noopener">Lab 2: Scrabble</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><ctype.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Points assigned to each letter of the alphabet</span></span><br><span class="line"><span class="keyword">int</span> POINTS[] = {<span class="number">1</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">1</span>, <span class="number">8</span>, <span class="number">5</span>, <span class="number">1</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">3</span>, <span class="number">10</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">4</span>, <span class="number">8</span>, <span class="number">4</span>, <span class="number">10</span>};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">compute_score</span><span class="params">(<span class="built_in">string</span> <span class="keyword">word</span>)</span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Get input words from both players</span></span><br><span class="line"> <span class="built_in">string</span> word1 = get_string(<span class="string">"Player 1: "</span>);</span><br><span class="line"> <span class="built_in">string</span> word2 = get_string(<span class="string">"Player 2: "</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Score both words</span></span><br><span class="line"> <span class="keyword">int</span> score1 = compute_score(word1);</span><br><span class="line"> <span class="keyword">int</span> score2 = compute_score(word2);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Print the winner</span></span><br><span class="line"> <span class="keyword">if</span> (score1 > score2)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Player 1 wins!\n"</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (score1 < score2)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Player 2 wins!\n"</span>);</span><br><span class="line"> } <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Tie!\n"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">compute_score</span><span class="params">(<span class="built_in">string</span> <span class="keyword">word</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Compute and return score for string</span></span><br><span class="line"> <span class="keyword">int</span> points = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, n = <span class="built_in">strlen</span>(<span class="keyword">word</span>); i < n; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">isupper</span>(<span class="keyword">word</span>[i]))</span><br><span class="line"> {</span><br><span class="line"> points += POINTS[<span class="keyword">word</span>[i] - <span class="number">65</span>];</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">islower</span>(<span class="keyword">word</span>[i]))</span><br><span class="line"> {</span><br><span class="line"> points += POINTS[<span class="keyword">word</span>[i] - <span class="number">97</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> points;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 Lab 1: Population Growth</title>
<link href="/2020/12/06/CS50-Lab-1-Population-Growth/"/>
<url>/2020/12/06/CS50-Lab-1-Population-Growth/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=Tpl7k8IOT6E" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這篇文章是我練習寫 Week 1 的作業(因為不知道要放在哪裡,才不會以後找不到,所以就決定放在部落格啦),歡迎大家有更好的解法可以一起討論唷~</p><p>題目:<a href="https://cs50.harvard.edu/college/2020/fall/labs/1/#:~:text=Lab%201:%20Population%20Growth" target="_blank" rel="noopener">Lab 1: Population Growth</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Prompt for start size</span></span><br><span class="line"> <span class="keyword">int</span> start_size;</span><br><span class="line"> <span class="keyword">do</span></span><br><span class="line"> {</span><br><span class="line"> start_size = get_int(<span class="string">"Start size: "</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (start_size < <span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Prompt for end size</span></span><br><span class="line"> <span class="keyword">int</span> end_size;</span><br><span class="line"> <span class="keyword">do</span></span><br><span class="line"> {</span><br><span class="line"> end_size = get_int(<span class="string">"End size: "</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (end_size < start_size);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Calculate number of years until we reach threshold</span></span><br><span class="line"> <span class="comment">// Big O(1)</span></span><br><span class="line"> <span class="keyword">int</span> year = (<span class="keyword">int</span>) <span class="built_in">floor</span>(<span class="built_in">log</span>( (<span class="keyword">double</span>)end_size/ (<span class="keyword">double</span>)start_size) / <span class="built_in">log</span>(<span class="number">13.0</span>/<span class="number">12.0</span>));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Big O(n)</span></span><br><span class="line"> <span class="comment">// int year = 0;</span></span><br><span class="line"> <span class="comment">// do</span></span><br><span class="line"> <span class="comment">// {</span></span><br><span class="line"> <span class="comment">// start_size = start_size + start_size/3 - start_size/4;</span></span><br><span class="line"> <span class="comment">// year += 1;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// while (start_size < end_size);</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Print number of years</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Years: %i\n"</span>, year);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 3 - Algorithms 筆記</title>
<link href="/2020/10/10/cs50-week-3-notes/"/>
<url>/2020/10/10/cs50-week-3-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=tk3wiMbQmow" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 3 筆記,想先看之前筆記的人可以點選下面連結:</p><ul><li><a href="/2020/09/12/cs50-week-0-notes">week 0</a></li><li><a href="/2020/09/19/cs50-week-1-notes">week 1</a></li><li><a href="/2020/09/27/cs50-week-2-notes">week 2</a></li></ul><h2 id="Running-Times"><a href="#Running-Times" class="headerlink" title="Running Times"></a>Running Times</h2><h3 id="Big-O"><a href="#Big-O" class="headerlink" title="Big O"></a>Big O</h3><p>用來表示執行所需要最大步驟(n)的最大時間</p><ul><li>O(n^2)</li><li>O(n log n)</li><li>O(n)</li><li>O(log n)</li><li>O(1)</li></ul><h3 id="Omega"><a href="#Omega" class="headerlink" title="Omega"></a>Omega</h3><p>用來表示執行所需要最小步驟(n)的最小時間</p><ul><li>Ω(n^2)</li><li>Ω(n log n)</li><li>Ω(n)</li><li>Ω(log n)</li><li>Ω(1)</li></ul><h3 id="Theta"><a href="#Theta" class="headerlink" title="Theta"></a>Theta</h3><p>當 Big O 和 Omega 相同的時候可以用 Theta 來表示</p><ul><li>Θ(n^2)</li><li>Θ(n log n)</li><li>Θ(n)</li><li>Θ(log n)</li><li>Θ(1)</li></ul><h2 id="Linear-Search"><a href="#Linear-Search" class="headerlink" title="Linear Search"></a>Linear Search</h2><p>講者用好幾個門表示 Array,當我們想要在這個 Array 中找到一個特定數字,我們從第一個門由左到右一個一個地打開,直到找到想要找的數字為止。</p><p>這樣的搜尋方式,如果用 Big O 表示的話就會是 <strong>O(n)</strong>,n 表示這個 Array 的長度,也就是說我們不幸的在最後一道門打開,才找到想要找的數字。<br>如果是用 Omega 來表示的話就會是 <strong>Ω(1)</strong>,因為我們可能很幸運地在第一個門打開,就找到我們要找的數字。</p><h2 id="Binary-Search"><a href="#Binary-Search" class="headerlink" title="Binary Search"></a>Binary Search</h2><p>當門後面的數字已經排序好了,我們就可以用 Binary Search,每次切一半搜尋,看打開的數字是比想要找的數字大還小,決定要繼續往左或右的中間搜尋(其實就是 week 0 的時候提到從電話簿中找電話的二分法),每次都從中間搜尋直到找到想要找的數字為止。</p><p>這樣的搜尋方式,如果用 Big O 表示的話就會是 <strong>O(log n)</strong>,n 表示這個 Array 的長度,log 則是以 2 為底數,如果有 8 個門,每次切一半,最多要切三次才能找到想要找的數字,即 2^3 = 8,也就是 log 8 = 3,故為 log n。<br>如果是用 Omega 來表示的話就會是 <strong>Ω(1)</strong>,因為我們可能很幸運地在中間的門打開,就找到我們要找的數字。</p><p>Binary Search 比 Linear Search 快得多,當 n 越大的時候會越明顯。</p><h2 id="strcmp"><a href="#strcmp" class="headerlink" title="strcmp"></a>strcmp</h2><p>在 c 語言中我們不能直接進行 string 的比較(理由會在下堂課程說明),而在 string.h 中提供給我們進行 string 比較的 function 叫做 <strong><a href="https://www.programiz.com/c-programming/library-function/string.h/strcmp" target="_blank" rel="noopener">strcmp</a></strong>,如果傳入的兩個 string 相同的話會回傳 0,如果第一個 string 比較大的話會回傳正數,如果比較小的話會回傳負數,其中 string 的大小是針對第一個不同的字母經過 <a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 轉換成數字比較後決定。</p><h2 id="structs"><a href="#structs" class="headerlink" title="structs"></a>structs</h2><p>透過以下程式碼,我們可以創造自己的 data type:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> <span class="built_in">string</span> name;</span><br><span class="line"> <span class="built_in">string</span> number;</span><br><span class="line">}</span><br><span class="line">person;</span><br></pre></td></tr></table></figure><p>可以用以下方式給值:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">person people[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">people[<span class="number">0</span>].name = <span class="string">"Cindy"</span>;</span><br><span class="line">people[<span class="number">0</span>].number = <span class="string">"09xx-xxx-xxx"</span>;</span><br></pre></td></tr></table></figure><h2 id="sorting"><a href="#sorting" class="headerlink" title="sorting"></a>sorting</h2><h3 id="Selection-Sort"><a href="#Selection-Sort" class="headerlink" title="Selection Sort"></a>Selection Sort</h3><p>當我們有一串數字需要做排序時,因為電腦只能一個一個的檢查,不像人類可以一眼看出哪個數字最小,所以電腦要從頭到尾一個一個的比較後,挑出最小的數字放到最左邊,並將左邊多出來的數字放到右邊有空缺的位置,接著排除已經排好的數字,繼續從頭到尾掃一次,找出最小的數字,直到排到最後一個數字為止。</p><p>假設總共有 n 個數字需要排序,排序的次數如下:<br><code>n + (n - 1) + (n - 2) + ... + 1</code> 因為每次都會排好一個數字,所以每次遞減 1<br><code>n(n + 1)/2</code><br><code>(n^2+n)/2</code><br><code>n^2/2 + n/2</code><br>當 n 越大的時候,具有影響力的會是 n^2,所以用 Big O 表示的話就會是 <strong>O(n^2)</strong>。<br>如果是用 Omega 來表示的話就會是 <strong>Ω(n^2)</strong>,因為就算已經排序好了我們還是得一個個的檢查。<br>可以用 <strong>Θ(n^2)</strong> 表示。</p><h3 id="Bubble-Sort"><a href="#Bubble-Sort" class="headerlink" title="Bubble Sort"></a>Bubble Sort</h3><p>從左到右每次兩兩比較並排序,如此會像泡泡跑到頂端一樣,最大的數字會最先到頂端(右邊)排好,重複進行兩兩比較,直到最後所有的數字都跑到比較中數字的頂端。</p><p>假設總共有 n 個數字需要排序,排序的次數如下:<br><code>(n - 1) * (n - 1)</code> 在 n - 1 次(0 到 n)的循環中重複 n - 1 次(從 0 到 n - 2 的兩兩比較)<br><code>n^2 - 1n - 1n + 1</code><br><code>n^2 - 2n + 1</code><br>當 n 越大的時候,具有影響力的會是 n^2,所以用 Big O 表示的話就會是 <strong>O(n^2)</strong>。<br>如果是用 Omega 來表示的話就會是 <strong>Ω(n)</strong>,因為第一次的兩兩比較發現不需要換任何數字的位置而停止(從 0 到 n - 2 的兩兩比較,實際上是 n - 1,但減 1 可以忽略不計)。</p><h3 id="Merge-Sort"><a href="#Merge-Sort" class="headerlink" title="Merge Sort"></a>Merge Sort</h3><p>將數字分成左右各半,分別對左半邊和右半邊進行排序,將排序的兩半合併,合併時每次進行左半和右半的比較,分成一半的時候切到最小單位進行,就可以重複進行合併的比較。</p><p>假設總共有 n 個數字需要排序,排序的次數如下:<br>每次會拿 n 個數字進行合併,因為每次對一半進行排序,所以總共移動 log n 次。<br>所以用 Big O 表示的話就會是 <strong>O(n log n)</strong>。<br>如果是用 Omega 來表示的話就會是 <strong>Ω(n log n)</strong>,因為就算已經排序好了我們還是得一個個的檢查。<br>可以用 <strong>Θ(n log n)</strong> 表示。</p><p>雖然這個排序方法更快,但它需要至少另一個 Array 的空間來暫存合併前的數字,即此演算法會需要兩倍的空間。</p><ul><li>Merge Sort 運用到 Recursion,在函數的定義中使用函數自身的方法(例如:切到最小單位重複進行相同步驟)。</li></ul><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>這堂課讓我對於各種演算法有了更深的認識,在寫程式時可以思考使用到邏輯的時間複雜度是什麼,以及考慮到是時間比較便宜還是空間比較便宜,選擇更適合當下情境的演算法。</p><ul><li><a href="https://www.youtube.com/watch?v=ZZuD6iUe3Pc" target="_blank" rel="noopener">Visualization and Comparison of Sorting Algorithms</a></li></ul>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 2 - Arrays 筆記</title>
<link href="/2020/09/27/cs50-week-2-notes/"/>
<url>/2020/09/27/cs50-week-2-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=v7Ho89RMRIo&t=851" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 2 筆記,想先看 <a href="/2020/09/12/cs50-week-0-notes">week 0</a> 和 <a href="/2020/09/19/cs50-week-1-notes">week 1</a> 筆記的各位觀眾可以點連結過去看看唷!</p><p>課程的一開始講師開始講解上週我們使用的指令 <code>make</code> …</p><h2 id="make-hello"><a href="#make-hello" class="headerlink" title="make hello"></a>make hello</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 此檔案為 hello.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">printf</span> ( <span class="string">"hello, world"</span> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上週我們使用 make 指令來做 compiling 的時候,輸入完指令後畫面會出現這些 <code>clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow hello.c -lcrypt -lcs50 -lm -o hello</code>,實際上我們在使用的指令其實是 <code>clang</code> 呢!</p><p>而當我們直接使用 <code>clang hello.c</code> 這個指令時,我們編譯出的可執行檔案叫做 <code>a.out</code>(多年前人類決定的預設名稱),而如果我們輸入的指令為 <code>clang -o hello hello.c</code> 時 (clang 後面的指令可以稱之為 command line arguments),這次指令的意思是<strong>編譯 hello.c 這個檔案且 output 的檔案要叫 hello</strong>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 此檔案為 hello.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">string</span> name = get_string(<span class="string">"What's your name? "</span>);</span><br><span class="line"> <span class="built_in">printf</span> (<span class="string">"hello, %s\n"</span>, name);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>當我們引用了 cs50 的 library 時,我們用同樣的 <code>clang -o hello hello.c</code> 指令是會出現 undefined reference to ‘get_string’ 的錯誤,因為我們沒有告訴電腦我們有使用到 cs50 的 library,這時候指令可以改成 <code>clang -o hello hello.c -lcs50</code>(l 表示 link),這樣就可以正常運作了,而 <code>make</code> 指令其實就是在幫我們自動做這些事情。</p><h2 id="Compiling"><a href="#Compiling" class="headerlink" title="Compiling"></a>Compiling</h2><p>上週我們簡單的知道從 source code 到 machine code 會經過 compiler,但 compiling 的過程其實可以細分成四個步驟:</p><ol><li><p>Preprocess<br>這個步驟將 header 檔案裡 function 的 prototype(原型) 寫到我們的檔案裡,例如上面的程式碼會變成這樣:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="function"><span class="built_in">string</span> <span class="title">get_string</span><span class="params">(<span class="built_in">string</span> prompt)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">printf</span><span class="params">(<span class="built_in">string</span> format, ...)</span></span>;</span><br><span class="line">...</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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">string</span> name = get_string(<span class="string">"What's your name? "</span>);</span><br><span class="line"> <span class="built_in">printf</span> (<span class="string">"hello, %s\n"</span>, name);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Compiling<br>將 source code(C) 轉變成另一種 source code 叫做 assembly code (對電腦的大腦(CPU)較友善的語言),看起來像這樣:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">main: # @main</span><br><span class="line"> .cfi_startproc</span><br><span class="line"># BB#0:</span><br><span class="line"> pushq %rbp</span><br><span class="line">.Ltmp0:</span><br><span class="line"> .cfi_def_cfa_offset 16</span><br><span class="line">.Ltmp1:</span><br><span class="line"> .cfi_offset %rbp, -16</span><br><span class="line"> movq %rsp, %rbp</span><br><span class="line">.Ltmp2:</span><br><span class="line"> .cfi_def_cfa_register %rbp</span><br><span class="line"> subq $16, %rsp</span><br><span class="line"> xorl %eax, %eax</span><br><span class="line"> movl %eax, %edi</span><br><span class="line"> movabsq $.L.str, %rsi</span><br><span class="line"> movb $0, %al</span><br><span class="line"> callq get_string</span><br><span class="line"> movabsq $.L.str.1, %rdi</span><br><span class="line"> movq %rax, -8(%rbp)</span><br><span class="line"> movq -8(%rbp), %rsi</span><br><span class="line"> movb $0, %al</span><br><span class="line"> callq printf</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure></li><li><p>Assembling<br>將 assembly code 轉換成最後的 machine code (0 和 1)。</p></li><li><p>Linking<br>將我們寫的程式碼已經轉換的 0 和 1 們,以及有用到的 library 程式碼轉換的 0 和 1 們全部合併再一起。</p></li></ol><h2 id="debugging"><a href="#debugging" class="headerlink" title="debugging"></a>debugging</h2><ul><li>printf:將我們需要的資訊印出來看看問題在哪裡。</li><li><a href="https://github.com/cs50/harvard.cs50.debug" target="_blank" rel="noopener">debug50</a>:CS50 提供給我們的工具,在 <a href="https://ide.cs50.io" target="_blank" rel="noopener">CS50 IDE</a> 中可以直接使用,在執行指令前輸入 debug50,例如 <code>debug50 ./hello</code>,但要先設定 breakpoint,程式才知道要停在哪裡。</li><li>Duck Debugging:說出來就會發現問題,跟桌上的小鴨說說話吧:)</li></ul><h2 id="C-data-types"><a href="#C-data-types" class="headerlink" title="C data types"></a>C data types</h2><p>1 byte(位元組) = 8 bits(位元)</p><ul><li>bool(布林):1 byte</li><li>char:1 byte</li><li>double:8 bytes</li><li>float:4 bytes</li><li>int:4 bytes</li><li>long:8 bytes</li><li>string:? bytes (依據長度有所不同)</li></ul><p>通常占多少空間依據電腦而有所不同。</p><h2 id="memory"><a href="#memory" class="headerlink" title="memory"></a>memory</h2><p>RAM(Random Access Memory):資料短暫儲存的記憶體(要插電才能運作),因為還沒有存到永久記憶體中而可能遺失,但速度快。<br>硬碟:資料永久儲存的地方。</p><p>當我們在執行程式的時候,變數會暫存在記憶體中,而佔用的空間可以參考上一段 C data types。</p><h2 id="arrays"><a href="#arrays" class="headerlink" title="arrays"></a>arrays</h2><p>由左至右有序的排列,例如:<code>int scores[3];</code>表示 3 個 integer 的 array,在電腦中慣例是從 0 開始數,所以 3 個數字會是 scores[0]、scores[1]、scores[2]。</p><p>在 function 中可以傳入 array 作為參數,範例如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">average</span><span class="params">(<span class="keyword">int</span> length, <span class="keyword">int</span> <span class="built_in">array</span>[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> sum / (<span class="keyword">float</span>) length;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="型別轉換"><a href="#型別轉換" class="headerlink" title="型別轉換"></a>型別轉換</h2><p>我們都知道了字母或符號在 <a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 都會有對應的數字,所以我們可以直接將 char 或符號轉換成 int,例如:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span> c = <span class="string">'#'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// print 35</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%i\n"</span>, c);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="strings"><a href="#strings" class="headerlink" title="strings"></a>strings</h2><p>我們將 char 放在 array 裡實現了 string (雙引號),所以我們可以 s[0] 取出 string 中的第一個字母,而電腦需要知道在記憶體中這個 string 是在哪裡結束(不會跟記憶體中的其他東西混在一起),string 會在最後多占一個 byte 存 <code>00000000</code>,或用 <code>\0</code> 表示 byte 中全是 0,又稱為 <code>NUL</code>,告訴電腦這裡是結束的位置,所以我們每次使用 string 都會多佔用一個 byte(00000000)。</p><ul><li>注意:我們在 C 語言可以訪問記憶體中的任何位置。例如我只有存了叫做 HI! 的 string,<code>string s = "HI!";</code> 卻可以透過 s[3] 得到 0,s[400] 得到記憶體中的某個東西。</li></ul><p>我們可以透過 <code>s[i] != '\0'</code> 來判斷是不是最後一個字母,或著我們可以直接用 <code>string.h</code> 提供給我們的方法 <code>strlen(s)</code> 來知道 string 的長度。</p><h2 id="英文大小寫轉換"><a href="#英文大小寫轉換" class="headerlink" title="英文大小寫轉換"></a>英文大小寫轉換</h2><p>再次觀察 <a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 表,我們可以知道 <code>s[i] >= 'a' && s[i] <= 'z'</code> 表示是小寫英文字母,而大寫英文和小寫英文都差了 32,所以可以利用這個特性做到大小寫轉換,而 <code>ctype.h</code> 提供給我們 islower function。</p><h2 id="Command-Line-Arguments"><a href="#Command-Line-Arguments" class="headerlink" title="Command-Line Arguments"></a>Command-Line Arguments</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">(<span class="keyword">int</span> argc, <span class="built_in">string</span> argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>argc 表示 argument count,使用者輸入的所有字的數量(包含程式指令)。</li><li>argv 表示 argument vector,使用者輸入的所有字(包含程式指令),以陣列中的 string 來表示。由於 string 本身也是 array,所以我們可以透過 array in array 的關係得到在 argv 中的每一個單字。</li></ul><h3 id="為什麼-main-回傳的是-integer?"><a href="#為什麼-main-回傳的是-integer?" class="headerlink" title="為什麼 main 回傳的是 integer?"></a>為什麼 main 回傳的是 integer?</h3><p>執行指令後會回傳代碼,0 表示正常,1 或其他數字表示錯誤,當我們執行完程式後可利用 <code>echo $?</code> 來確認上一步驟執行的程式最後回傳的值是什麼,以利於發生錯誤時的檢測。</p><h2 id="Cryptography"><a href="#Cryptography" class="headerlink" title="Cryptography"></a>Cryptography</h2><p>當我們在傳紙條的時候不會希望紙條的內容被其他人看懂,這時候我們可能會用類似暗號的方式,而暗號通常會有一個對照表找出暗號要表達的意思,這樣的話只要知道對照表的人就可以知道紙條的內容了,而這時候我們就會想要作加密這件事情,例如我們可能本來想要傳遞的訊息是 <strong>I LOVE YOU</strong> 而我們可以利用 <a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 找到對應的數字 <strong>73 76 79 86 89 79 85</strong>,接著將每個值都 +1,內容會變成 <strong>74 77 80 87 90 80 86</strong>,接著再利用 <a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 將數字轉換成英文,所以我們最終的結果會變成 <strong>J MPWF ZPV</strong>,其中 key 是 1,plaintext 是 I LOVE YOU,經過 cipher 加密後 ciphertext 為 J MPWF ZPV,如此的話只有知道如何加密且擁有 key 的人才會知道要怎麼解密。</p><p>過程示意如下:</p><p>key -><br>plaintext -> <em>cipher</em> -> ciphertext</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>這堂課針對前幾堂課的內容做更深入點的說明,讓我們認識 array,也讓我們了解 string 的運作是利用將單字放在 array 裡而達成的,並且了解了字母或符號是如何可以做型別轉換…等等,是內容豐富的一堂課,如果想知道更多的話可以參考<a href="https://cs50.harvard.edu/college/2020/fall/weeks/2" target="_blank" rel="noopener">官網</a>唷!</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 1 - C 筆記</title>
<link href="/2020/09/19/cs50-week-1-notes/"/>
<url>/2020/09/19/cs50-week-1-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=iRkKEHybf9M&start=490&t=0s" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>今天這篇是 CS50 week 1 筆記,想先看 week 0 筆記的各位觀眾可以先點 <a href="/2020/09/12/cs50-week-0-notes">這裡</a> 唷!</p><p>這堂課運用了上一堂課學的 scratch 與 C 語言的對應程式碼,來讓學生更容易進入 C 語言的世界呢!</p><p>一開始講者提醒學生們,寫程式碼除了要 correct 以外,也應該要是 well design & well style。</p><h2 id="compiler"><a href="#compiler" class="headerlink" title="compiler"></a>compiler</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></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 class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">printf</span> ( <span class="string">"hello, world"</span> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我們寫了一段程式碼,其中 printf () 中的 f 代表 format,分號告訴電腦結束,但是依照上一堂課學到的,電腦只懂 binary,也就是一切用 1 和 0 表示的世界,所以我們會需要作轉換,讓電腦讀懂我們寫的程式碼,而這轉換的方式我們稱為 compiler。</p><p>過程如下:<br>source code -> <strong>compiler</strong> -> machine code</p><p>指令:</p><ul><li>clang 將 source code 轉成 machine code。</li><li>make + 名稱,這個指令會做 compiler ,並用 make 後面的名稱產生同名可執行的檔案,例如 make hello。</li></ul><p>最後會產生一個 hello 檔案,接著可以用 <code>./hello</code> 執行產生的檔案。</p><h2 id="header-files"><a href="#header-files" class="headerlink" title="header files"></a>header files</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cs50.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br></pre></td></tr></table></figure><p>.h 表示 header,<code>#include <stdio.h></code> 讓我們可以直接使用前人寫好的 printf function。<br><code>#include <cs50.h></code> 讓我們可以直接使用 cs50 提供給我們的 library。</p><h2 id="錯誤"><a href="#錯誤" class="headerlink" title="錯誤"></a>錯誤</h2><p>當出錯時翻到最上面找錯誤,因為錯誤可能在一開始就發生並且導致後面其他堆疊的錯誤出現,錯誤訊息會告訴我們錯誤發生在第幾行第幾列,另外可以使用 cs50 團隊寫好的 <a href="https://github.com/cs50/help50" target="_blank" rel="noopener">help50</a> 指令獲得更清楚的錯誤訊息,例如執行 <code>help50 make hello</code>。</p><h2 id="style"><a href="#style" class="headerlink" title="style"></a>style</h2><p>可以使用 <a href="https://cs50.readthedocs.io/style50/" target="_blank" rel="noopener">style50</a> 做風格檢查,例如執行 <code>style50 hello.c</code>,做風格檢查最重要的目的是要寫出可讀性高的程式碼。</p><h2 id="註解"><a href="#註解" class="headerlink" title="註解"></a>註解</h2><p>註解應該是要講這個程式的目的,而不是講這個程式碼在做什麼,因為這個程式碼在做什麼直接看程式碼就知道了,不需要再寫一次。</p><h2 id="Command-Line"><a href="#Command-Line" class="headerlink" title="Command-Line"></a>Command-Line</h2><p>一些常用的指令:cd, cp, ls, mkdir, mv, rm, rmdir。</p><h2 id="Types"><a href="#Types" class="headerlink" title="Types"></a>Types</h2><p>可以參考<a href="https://en.wikipedia.org/wiki/C_data_types" target="_blank" rel="noopener">這裡</a>。</p><ul><li>int: integer,表示只使用 32 位元的整數,超過 40 億的數字將無法計算</li><li>long long: long integer,64 位元的整數。</li><li>float: 浮點數是具有小數點的數字,只使用 32 位元。</li><li>double: 使用 64 位元的 float,讓我們可以做更精確的計算。</li><li>char: 要用單引號不能用雙引號,表示一個字符(byte)。</li></ul><p>如果用 integer 相除的話,會回傳 integer 的結果,可以直接在變數前面用括號重新定義 type。<br>例如:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> z = (<span class="keyword">float</span>) x / (<span class="keyword">float</span>) y</span><br></pre></td></tr></table></figure><h2 id="語法"><a href="#語法" class="headerlink" title="語法"></a>語法</h2><p>將以下這段程式碼放在檔案上面,表示先告訴電腦有這些定義的 function。<br>第一個 void 表示沒有任何返回值,第二個 void 表示不接收任何輸入。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">meow</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>第一個 void 表示沒有任何返回值,第二個 int n 表示接收 integer n 這個引數。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">meow</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span> ( <span class="string">"meow\n"</span> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="float-的限制"><a href="#float-的限制" class="headerlink" title="float 的限制"></a>float 的限制</h2><p>當我們的數字是無限大時,或我們想要顯示小數點後面更多的數字時,電腦會因為記憶體有限的關係,在某位數做四捨五入讓數字變的不是那麼精確。越精確就會需要更大的空間。但我們無法精確的計算無限大的數字,因為我們沒有無限大的空間。</p><h2 id="integer-overflow"><a href="#integer-overflow" class="headerlink" title="integer overflow"></a>integer overflow</h2><p>因為電腦的硬體容量是有限的,假設我們只有 3 位元的容量時,當我們要從二進位的 111 再繼續進一位的時候,就會變成 000,應該是 1000,但因為總共只有 3 位元,所以進一位之後只剩下後面的 3 位元,而出錯了,<a href="https://en.wikipedia.org/wiki/Year_2000_problem" target="_blank" rel="noopener">Y2K bug</a> 就是因為這樣的問題而產生的 bug。而因為目前我們是用 32 位元來計算時間,也就是說最多只能算到 40 億,而未來 2038/1/19 我們將會面臨到一樣的問題。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>這堂課大部分的重點都在教 C 語言的語法,筆記就不詳細寫了,有興趣的人也可以直接看官方的<a href="https://cs50.harvard.edu/college/2020/fall/notes/1" target="_blank" rel="noopener">筆記</a>,老實說 C 跟 Ruby 寫起來還真是像,不愧是 CRuby (?),大概只差在 C 要先做型別的宣告然後多了比較醜的大括號XD,少了人性化的寫法(?)。</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>CS50 week 0 - Scratch 筆記</title>
<link href="/2020/09/12/cs50-week-0-notes/"/>
<url>/2020/09/12/cs50-week-0-notes/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近跟同事小夥伴相約一起看 CS50 的課程,CS50 (Introduction to Computer Science)是一堂美國哈佛大學知名的通識課程,完全免費,在 <a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" target="_blank" rel="noopener">edx</a> 或 <a href="https://www.youtube.com/watch?v=Tpl7k8IOT6E" target="_blank" rel="noopener">youtube</a> 或 <a href="https://github.com/athena-xcy/CS50-Study-Group" target="_blank" rel="noopener">CS50-Study-Group github</a> 都可以非常容易地看到。</p><p>這系列的文章會是我的個人筆記,歡迎有興趣的人一定要自己去看看 CS50 的課程歐。</p><p>開始看影片就被講者激動的演說方式感動到,就像是用盡全力要讓看影片的人聽懂一樣,覺得很棒推薦給大家。</p><h2 id="什麼是-Computer-Science"><a href="#什麼是-Computer-Science" class="headerlink" title="什麼是 Computer Science"></a>什麼是 Computer Science</h2><p>Computer Science is about problem solving,即 CS 就是解決問題的科學,看到這裡不免點頭同意,在工作中我們確實是不停地要去思考如何才能夠解決客戶提出的問題呢!完全不是躲在電腦前面這麼一回事,必須要想辦法去理解客戶需要解決的問題是什麼,並且提出解決問題的方案。所以我們會有個 Input(待解決的問題) 以及 Output(解答),而這整個過程,就是 Computer Science。</p><h2 id="Binary"><a href="#Binary" class="headerlink" title="Binary"></a>Binary</h2><p>Computer 只有 0 和 1,就像是開燈或關燈,通電或不通電,對電腦來說其實這樣就足夠了,因為他只需要知道有跟沒有,就可以運用 mapping 的方式知道說例如開啟電源是 1 而關閉電源是 0。</p><p>二進位中每個 bits 可以表示 1 和 0,而當我們有更多的 bits 時我們可以數更大的數字,例如二進位的 111(三 bits) 是十進位的 7,1111(四 bits) 是 15。</p><h2 id="ASCII"><a href="#ASCII" class="headerlink" title="ASCII"></a>ASCII</h2><p><a href="https://www.asciichart.com" target="_blank" rel="noopener">ASCII</a> 設計出可以表示字母的數字,例如大寫的 A 是用十進位的 65 表示,或說是二進位的 01000001,電腦會由環境的 context 決定是數字或字母等等。ASCII 一個字(byte) 只有 8 個 bits 故只能排列組合出 256 種不同的字符。</p><h2 id="抽象"><a href="#抽象" class="headerlink" title="抽象"></a>抽象</h2><p>如果我們將電腦直接理解的低階概念移到了人類更好理解的階層,就是抽象。<br>這讓我想到我們通常覺得比較容易理解的是高階的程式語言,感覺有點相似。</p><h2 id="Unicode"><a href="#Unicode" class="headerlink" title="Unicode"></a>Unicode</h2><p>當人們發現 ASCII 已經不夠用了,開始有了 Unicode 的出現,有 8 bits、16 bits、24 bits,甚至 32 bits,有了如此巨大的空間,所以我們可以有更多更多的特殊符號,而我們常聽到的 UTF-8 就是 Unicode 的一種。</p><p>當我們在電腦畫面上看到 😂,實際上是十進位的 128514,二進位的 000000011111011000000010 呢。</p><h2 id="RGB"><a href="#RGB" class="headerlink" title="RGB"></a>RGB</h2><p>RGB(Red、Green、Blue),當 context 是照片這類跟顏色相關的情況時,由三個 byte 表示分別需要多少 pixel 的紅色、綠色和藍色,而每個 byte 是 8 bits,用十進位表示的話就是每個 byte 有 0-255 可以表示。</p><p>而圖片透過這些顏色(每個點的 RGB)的表示來顯示出來,影片則是透過不同時間顯示不同圖片來表示。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>音樂也可以透過量化的方式來表示,例如某個聲音用什麼數字表示,並用某個數字表示要持續此聲音多久等等。</p><p>而我們人類同意用什麼樣的模式來表示之後,讓電腦如此運作,所以我們有各種不同的檔案型態,但這一切對電腦來說,其實也都只是 1 和 0 的各種變化呢。</p><h2 id="Algorithms"><a href="#Algorithms" class="headerlink" title="Algorithms"></a>Algorithms</h2><p>演算法就是一開始說的 Input(待解決的問題) 以及 Output(解答),這中間一步步解決問題的過程。</p><p>講者用在電話簿裡找到特定的對象舉例,想到的演算法有三種:</p><ol><li>從頭翻到尾一頁一頁慢慢找。</li><li>每兩頁翻一次,可能會錯過而需要再翻回去找。</li><li>每次對分一半,看要找的對象在前半還是後半(電話簿有按照順序排列)。 => 這就是常聽到的二分法。</li></ol><p>用二分法可以更有效率的解決問題,因為每次都先解決了一半的問題。<br>這讓我想到以前在寫類似 <a href="https://leetcode.com" target="_blank" rel="noopener">leetcode</a> 的問題的時候都要思考會不會有時間複雜度的問題呢,在思考演算法的時候時間也是要考慮的因素。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>最後講者先用 Pseudocode (用英文直接表示程式邏輯) 帶著大家理解:Function、Condition、Boolean expressions、Loops</p><p>接著用 <a href="https://scratch.mit.edu" target="_blank" rel="noopener">scratch</a> 做為大家的第一個程式語言,並提醒大家拆解分析的重要。</p><p>總之是一堂讓人收穫良多的課程啊。</p>]]></content>
<tags>
<tag> CS50 </tag>
<tag> Computer Science </tag>
</tags>
</entry>
<entry>
<title>Clean Code - 函式</title>
<link href="/2020/08/30/clean-code-function/"/>
<url>/2020/08/30/clean-code-function/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,相信看過 <a href="/2020/06/06/clean-code">Clean Code - 無瑕的程式碼</a> 這篇文章的朋友們已經知道什麼是 Clean Code 了,之後會陸續針對每個章節做重點整理,以龜速進行中。</p><p><a href="https://amzn.to/3kSY9ug" target="_blank" rel="noopener">無瑕的程式碼-敏捷軟體開發技巧守則 (Clean Code: A Handbook of Agile Software Craftsmanship)</a> 第三章函式,我想就我們寫 Ruby 而言其實可以對應到我們所謂的<strong>方法</strong>。</p><h2 id="簡短!"><a href="#簡短!" class="headerlink" title="簡短!"></a>簡短!</h2><p>關於函式的首要準則,就是要簡短。第二項準則,就是<strong>要比第一項的簡短函式還要更短簡短</strong>。看到書中這段話有相當深刻的感受,當我們在看一段很長的函式(或方法),其實真的很常會看到不知道在哪裡,懷疑人生。另外想跟大家提一下 <a href="https://thoughtbot.com/blog/sandi-metz-rules-for-developers" target="_blank" rel="noopener">Sandi Metz’ Rules For Developers</a> thoughtbot 的這篇文章,其中第二條規則 <strong>Methods can be no longer than five lines of code.</strong>,跟 clean code 這本書提到的簡短其實都是一樣的道理。希望每個函式(或方法)都一清二楚,透露出本身的意圖。</p><h2 id="只做一件事情"><a href="#只做一件事情" class="headerlink" title="只做一件事情"></a>只做一件事情</h2><p>函式應該做一件事情。他們應該把這件事做好。而且他們應該只做這件事。<br>如果函式只做了函式名稱下<strong>同一層抽象概念</strong>的幾個步驟,那麼,這個函式就算是只做了一件事。觀察函式是否超過<strong>一件事情</strong>的另一種方法,是看你是否能夠從此函式中,提煉出另一個新函式,但此新函式不能只是重新詮釋原函式的實現過程(實作)而已。</p><h3 id="函式的段落"><a href="#函式的段落" class="headerlink" title="函式的段落"></a>函式的段落</h3><p>做一件事的函式沒有辦法被合理的分成不同段落。</p><h2 id="由上而下閱讀程式碼:降層準則"><a href="#由上而下閱讀程式碼:降層準則" class="headerlink" title="由上而下閱讀程式碼:降層準則"></a>由上而下閱讀程式碼:降層準則</h2><p>我們希望程式的閱讀就像是由上而下的敘事。我們希望每個函式的後面都緊接著<strong>下一層次的抽象概念</strong>,如此,我們在閱讀程式可依照看到的一連串函式,對應著抽象層次降層閱讀。這個方法就叫做降層準則。</p><h2 id="Switch-敘述-或-if-else"><a href="#Switch-敘述-或-if-else" class="headerlink" title="Switch 敘述 (或 if/else)"></a>Switch 敘述 (或 if/else)</h2><p>Switch 敘述總是在做 N 件事情,雖然我們無法避開使用 Switch 敘述,但我們能確保讓每個 Switch 敘述都被深埋在較低層次的類別裡,而且他永遠都不會被重複使用。我們可以利用多型(Polymorphism)來達到這樣的目的。</p><p>關於 Ruby 如何利用多型(Polymorphism)來達到這樣的目的,可參考 <a href="https://www.youtube.com/watch?v=mpA2F1In41w&t=1728" target="_blank" rel="noopener">hafentalks #7 - Sandi Metz: “Go Ahead, Make a Mess”</a>。</p><h2 id="使用具描述能力的名稱"><a href="#使用具描述能力的名稱" class="headerlink" title="使用具描述能力的名稱"></a>使用具描述能力的名稱</h2><p>當每個你看到的程式,執行結果都與你想的差不多,你會察覺到你正工作在 Clean Code 之上。<br>別害怕去取較長的名稱,一個較長但具描述性質的名稱,比一個較短但難以理解的名稱還要好。</p><h2 id="函式的參數"><a href="#函式的參數" class="headerlink" title="函式的參數"></a>函式的參數</h2><p>函式的參數數量,最理想的是零個(零參數函式;niladic),其次是一個(單參數函式;monadic),再不然就是兩個(雙參數函式;dyadic)。可以的話,盡量避免使用三個參數(三參數函式;triadic)。如果要使用超過三個參數(多參數函式;polyadic),必須有非常特殊的理由—否則無論如何都不應該如此做。</p><h3 id="單一參數的常見形式"><a href="#單一參數的常見形式" class="headerlink" title="單一參數的常見形式"></a>單一參數的常見形式</h3><ol><li>會問與這個參數有關的問題。</li><li>對這個參數進行某種操作。</li><li>事件。這類的形式比較少見,在這類型中會有輸入型參數,但是不會有輸出型參數。</li></ol><h3 id="旗標-flag-參數"><a href="#旗標-flag-參數" class="headerlink" title="旗標(flag)參數"></a>旗標(flag)參數</h3><p>使用 flag 參數是一種很爛的做法。將一個布林變數傳遞給函式,是一種非常恐怖的習慣。這馬上會使得方法的署名(signature)變得複雜,等同於大聲宣布此函式做了不只一件事。</p><h3 id="物件型態的參數"><a href="#物件型態的參數" class="headerlink" title="物件型態的參數"></a>物件型態的參數</h3><p>當一個函式看起來需要超過兩個或三個的參數時,很可能需要將當中的一些參數包裝在一個類別裡。利用建立物件的方式,減少函式參數的數量,當一堆變數一起被傳遞時,他們是某個概念裡的相似部分,而這個概念應該獲得一個屬於他的名稱。</p><h3 id="動詞和關鍵字"><a href="#動詞和關鍵字" class="headerlink" title="動詞和關鍵字"></a>動詞和關鍵字</h3><p>替函式選一個好名稱,可以產生許多良好的附加價值,例如解釋函式的意圖、解釋函式參數的順序性及意圖。</p><h2 id="要無副作用"><a href="#要無副作用" class="headerlink" title="要無副作用"></a>要無副作用</h2><p>副作用(Side effects)就像是謊言。你的函式保證只做一件事情,卻暗地裡偷偷做了其他事情。有時候會使得同類別的其他變數,產生不可預期的改變。常會導致奇怪的時空耦合(temporal coupling)和順序相依性的問題。如果你必須有一個時空耦合,你應該在函式的名稱中說明清楚。</p><h2 id="指令和查詢的分離"><a href="#指令和查詢的分離" class="headerlink" title="指令和查詢的分離"></a>指令和查詢的分離</h2><p>函式應該要能做某件事,或能回答某個問題,但兩者不該同時發生。</p><h2 id="錯誤處理就是一件事"><a href="#錯誤處理就是一件事" class="headerlink" title="錯誤處理就是一件事"></a>錯誤處理就是一件事</h2><p>當你使用例外處理,而非使用錯誤碼時,新的例外可由例外類別衍生出來,不必被迫重新編譯和重新部署,就可以加入到現有的程式中。</p><h2 id="不要重複自己-DRY-Don’t-Repeat-Yourself"><a href="#不要重複自己-DRY-Don’t-Repeat-Yourself" class="headerlink" title="不要重複自己 DRY(Don’t Repeat Yourself)"></a>不要重複自己 DRY(Don’t Repeat Yourself)</h2><p>重複會讓程式變得擁擠,當演算法某些地方需要改變時,這些修改的地方需要花費 N 倍的工夫,因此也讓遺漏而導致的錯誤,發生的機會變 N 倍。</p><p>重複程式碼也許是軟體裡所有邪惡的根源。許多準則或慣例都是為了控制或移除他而發明的。例如,資料庫的 Codd’s normal forms(柯德正規法)是用來消除資料的重複。又例如,物件導向程式設計是利用將程式碼集中到基本的類別裡,來避免冗餘(redundant)。結構化程式設計、剖面導向程式設計(Aspect Oriented Programming)、元件導向程式設計(Component Oriented Programming)等。</p><h2 id="結構化程式設計"><a href="#結構化程式設計" class="headerlink" title="結構化程式設計"></a>結構化程式設計</h2><p>每個函式,及每個函式裡的區塊,都應該只有一個進入點及一個離開點。要遵守這個準則,代表在一個函式裡,只能有一個 return 敘述,迴圈內不能有任何的 break 或 continue 敘述,而且永遠不能有 goto 敘述。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>今天的重點整理就到這邊了,還記得我曾經寫過指令和查詢混在一起的方法,被公司 Fred 大大告訴我不應該這樣寫,之後也在 clean code 看到一樣的概念,覺得熟悉,但確實我應該讓方法的命名可以直接看出來是做什麼,如果是要問他是什麼狀態,就不應該邊問邊更改他的狀態啊啊啊。</p>]]></content>
<tags>
<tag> Clean Code </tag>
<tag> Coding </tag>
<tag> Agile </tag>
</tags>
</entry>
<entry>
<title>新手不小心會踩到的坑:我真的複製出物件了嗎?</title>
<link href="/2020/06/25/ruby-pass-by-reference-and-value/"/>
<url>/2020/06/25/ruby-pass-by-reference-and-value/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,幾天前公司早上例會的時候,討論了關於 Ruby 的指標,這次的討論讓我明白原來 Ruby 是用 rvalue 來實作,是 pass by value 但如果說超過特定長度的話這個 value 會有一個在 C 語言中實做的參考,讓我想到去年我在公司的<a href="https://5xruby.tw/posts/ruby-pass-by-reference-and-value" target="_blank" rel="noopener">官網</a>也有寫過一篇關於這件事情的文章,所以決定將這篇文章也分享在我自己的部落格裡,以下是原文。</p><hr><p>大家好,今天要來跟大家說一個如果是非本科系的工程師不確定上課有沒有學過,如果是本科系的應該是上課有學過,菜鳥工程師可能會不小心踩到的坑,在開始說明之前先給大家看一段程式碼。</p><p>這是一段很簡單的 Ruby 程式碼,表示調整廣播電台頻道的類別,支援預設電台的功能,也就是說使用者在按下某個按鈕,就可以跳到喜歡的頻道(話說現在年輕人還知道電台嗎?@@)。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tuner</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(presets)</span></span></span><br><span class="line"> @presets = presets</span><br><span class="line"></span><br><span class="line"> clean</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> private</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">clean</span></span></span><br><span class="line"> @presets.delete_if { <span class="params">|preset|</span> preset[-<span class="number">1</span>].to_i.even? }</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">p presets = <span class="string">%w(90.1 106.2 88.5)</span> <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br><span class="line">p turner = Tuner.new(presets) <span class="comment"># -> <Tuner:0x00007fe7b08ff170 <span class="doctag">@presets</span>=["90.1", "88.5"]></span></span><br><span class="line">p presets <span class="comment"># -> ["90.1", "88.5"]</span></span><br></pre></td></tr></table></figure><p>大家發現了嗎? presets 已經被我們改變了,在討論為什麼之前我們先來看看有什麼方式可以傳遞引數!</p><h2 id="傳遞引數的方式"><a href="#傳遞引數的方式" class="headerlink" title="傳遞引數的方式"></a>傳遞引數的方式</h2><ol><li><p>Pass-by-reference:丟到方法裡的引數實際上只是變數的參考(reference),修改引數就會修改到原始的變數。</p></li><li><p>Pass-by-value:丟到方法裡的引數是變數的值(value),修改引數不會修改到原始的變數。</p></li></ol><h2 id="Ruby-的引數是如何傳遞?"><a href="#Ruby-的引數是如何傳遞?" class="headerlink" title="Ruby 的引數是如何傳遞?"></a>Ruby 的引數是如何傳遞?</h2><p>參考<a href="https://www.ruby-lang.org/en/documentation/faq/4/" target="_blank" rel="noopener">官網的FAQ</a>:</p><p>在 Ruby 中所有的變數和常數都會指向一個參考物件,除了直接使用會噴 NameError 例外錯誤的未初始化區域變數(沒有參考),當我們指派一個變數或初始化一個常數,表示我們設定了變數或常數指向的參考物件。</p><p>意思是指派這件事情,實際上並不會產生一個複製的新物件,例如前面範例程式碼中 <code>presets = %w(90.1 106.2 88.5)</code> 表示說 <code>presets</code> 是會指向 <code>%w(90.1 106.2 88.5)</code> 參考物件的變數。</p><p>但 <code>Fixnum</code>, <code>true</code>, <code>nil</code>, 和 <code>false</code> 是例外,他們是 <code>immediate values</code>,變數會保持本身的物件而不是另外指向一個參考物件,這些例外被指派的引數會產生這個類型的複製物件。</p><p>當方法被調用時,引數被指派為參數:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">addOne</span><span class="params">(n)</span></span></span><br><span class="line"> n += <span class="number">1</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> a = <span class="number">1</span></span><br><span class="line">p addOne(a) <span class="comment"># -> 2</span></span><br><span class="line">p a <span class="comment"># -> 1</span></span><br></pre></td></tr></table></figure><p>當傳遞的是物件的參考時,一個方法是有可能改變傳進來的可變物件(mutable object):</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">downer</span><span class="params">(string)</span></span></span><br><span class="line"> string.downcase!</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">p a = <span class="string">"HELLO"</span> <span class="comment"># -> "HELLO"</span></span><br><span class="line">p downer(a) <span class="comment"># -> "hello"</span></span><br><span class="line">p a <span class="comment"># -> "hello"</span></span><br></pre></td></tr></table></figure><h3 id="小結:"><a href="#小結:" class="headerlink" title="小結:"></a>小結:</h3><ul><li>Ruby 傳遞引數的方法並沒有相當於其他語言的 pass-by-reference。</li><li>Ruby 是 pass-by-value,但是 values 是 references,Pass by reference value 可能是相對準確的說法。</li></ul><hr><p>回頭看看上面的 code,把 <code>object_id</code> 印出來觀察,presets 改變的原因是因為傳進方法的是可變物件的參考(相同 <code>object_id</code>),當我們直接改變物件就直接改到了原來的物件。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tuner</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(presets)</span></span></span><br><span class="line"> @presets = presets</span><br><span class="line"></span><br><span class="line"> clean</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> private</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">clean</span></span></span><br><span class="line"> @presets.delete_if { <span class="params">|preset|</span> preset[-<span class="number">1</span>].to_i.even? }</span><br><span class="line"> p @presets.object_id <span class="comment"># -> 70140344834080</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">p presets = <span class="string">%w(90.1 106.2 88.5)</span> <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br><span class="line">p presets.object_id <span class="comment"># -> 70140344834080</span></span><br><span class="line">p turner = Tuner.new(presets) <span class="comment"># -> <Tuner:0x00007fe7b08ff170 <span class="doctag">@presets</span>=["90.1", "88.5"]></span></span><br><span class="line">p presets <span class="comment"># -> ["90.1", "88.5"]</span></span><br></pre></td></tr></table></figure><h2 id="解決方法"><a href="#解決方法" class="headerlink" title="解決方法"></a>解決方法</h2><p>上面的範例,看起來可以改成這樣:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tuner</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(presets)</span></span></span><br><span class="line"> @presets = clean(presets)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> private</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">clean</span><span class="params">(presets)</span></span></span><br><span class="line"> presets.reject { <span class="params">|preset|</span> preset[-<span class="number">1</span>].to_i.even? }</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">p presets = <span class="string">%w(90.1 106.2 88.5)</span> <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br><span class="line">p turner = Tuner.new(presets) <span class="comment"># -> <Tuner:0x00007f9adfaa2b80 <span class="doctag">@presets</span>=["90.1", "88.5"]></span></span><br><span class="line">p presets <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br></pre></td></tr></table></figure><p>因為 reject 會以傳回一個新的陣列代替更改接收端(presets),故原本的物件並不會被我們修改到,但這並不一定是個好方法,如果我們不需要在初始化的階段就更改 presets,或是其他實體的方法才需要更改 presets,還是有可能會遇到麻煩。</p><p><strong>所以其實我們真正想要的是,可以用複製物件來代替指向原本物件的參考。</strong></p><h2 id="Ruby-複製物件的方法"><a href="#Ruby-複製物件的方法" class="headerlink" title="Ruby 複製物件的方法"></a>Ruby 複製物件的方法</h2><p>Ruby 有兩種複製物件的方法:<code>dup</code> 和 <code>clone</code>,雖然兩者都會根據接收端建立新物件,但 <code>clone</code> 會保存原始物件的 2 項額外功能,<code>dup</code> 則不會。</p><h3 id="clone-不同於-dup-的-2-項額外功能:"><a href="#clone-不同於-dup-的-2-項額外功能:" class="headerlink" title="clone 不同於 dup 的 2 項額外功能:"></a><code>clone</code> 不同於 <code>dup</code> 的 2 項額外功能:</h3><ol><li><p><code>clone</code> 會顧及接收端的凍結狀態(frozen status),如果原始物件已經凍結,那副本也會是凍結的。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">object = Object.new</span><br><span class="line">object.freeze</span><br><span class="line">p object.frozen? <span class="comment"># -> true</span></span><br><span class="line">dup_object = object.dup</span><br><span class="line">p dup_object.frozen? <span class="comment"># -> false</span></span><br><span class="line">clone_object = object.clone</span><br><span class="line">p clone_object.frozen? <span class="comment"># -> true</span></span><br></pre></td></tr></table></figure></li><li><p>如果接收端有單例方法,<code>clone</code> 會複製單例類別(singleton class)。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">object = Object.new</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">object</span>.<span class="title">wow</span>;</span> <span class="symbol">:wow</span> <span class="keyword">end</span></span><br><span class="line">p object.singleton_methods <span class="comment"># -> [:wow]</span></span><br><span class="line">p object.wow <span class="comment"># -> :wow</span></span><br><span class="line">clone_object = object.clone</span><br><span class="line">dup_object = object.dup</span><br><span class="line">p clone_object.wow <span class="comment"># -> :wow</span></span><br><span class="line">p dup_object.wow <span class="comment"># -> undefined method `wow' for #<Object:0x00007f7effa8af40> (NoMethodError)</span></span><br></pre></td></tr></table></figure></li></ol><p>所以當我們想修改物件的時候,大部分會選則用 <code>dup</code> 而不是 <code>clone</code>,因為凍結的物件無法修改或解凍,所以 <code>clone</code> 可能會回傳無法修改的物件。</p><h2 id="修改程式碼"><a href="#修改程式碼" class="headerlink" title="修改程式碼"></a>修改程式碼</h2><p>所以我們現在將程式碼改成這樣:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tuner</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(presets)</span></span></span><br><span class="line"> @presets = presets.dup</span><br><span class="line"></span><br><span class="line"> clean</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> private</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">clean</span></span></span><br><span class="line"> @presets.delete_if { <span class="params">|preset|</span> preset[-<span class="number">1</span>].to_i.even? }</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">p presets = <span class="string">%w(90.1 106.2 88.5)</span> <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br><span class="line">p turner = Tuner.new(presets) <span class="comment"># -> <Tuner:0x00007fe7b08ff170 <span class="doctag">@presets</span>=["90.1", "88.5"]></span></span><br><span class="line">p presets <span class="comment"># -> ["90.1", "106.2", "88.5"]</span></span><br></pre></td></tr></table></figure><p>恭喜!這樣就解決了我們一開始更改到原始物件的問題了!</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>雖然我們已經解決問題了,但要留意 <code>dup</code> 和 <code>clone</code> 只會產生淺層複製(shallow copy)的物件,意思是對於 <code>Array</code> 這類的集合物件,雖然複製了容器,但並未複製容器內的元素,我們可以在不影響原始物件的情況下加入或移除元素,但是如果我們試圖要修改 <code>Array</code> 裡的元素,就會修改到原始元素,例如:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">array = [<span class="string">"Polar"</span>]</span><br><span class="line">p dup_array = array.dup << <span class="string">"Bear"</span> <span class="comment"># -> ["Polar","Bear"]</span></span><br><span class="line">p dup_array.each { <span class="params">|element|</span> element.sub!(<span class="string">"lar"</span>, <span class="string">"oh"</span>) } <span class="comment"># -> ["Pooh", "Bear"]</span></span><br><span class="line">p array <span class="comment"># -> ["Pooh"]</span></span><br></pre></td></tr></table></figure><p>可以看到我們還是修改到原始物件的元素了,編寫自己的類別的時候我們可以複寫 <code>initialize_copy</code> 這個方法來控制複製過逞的深度,如果需要使用既有類別深層副本的物件,就必須自行處理。</p><p>另外有一個方法是可以使用 <a href="https://ruby-doc.org/docs/ruby-doc-bundle/ProgrammingRuby/book/ref_m_marshal.html" target="_blank" rel="noopener">Marshal</a> 類別來序列化,然後再對集合及其元素進行序列化還原:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">array = [<span class="string">"Polar"</span>]</span><br><span class="line">p dup_array = Marshal.load(Marshal.dump(array)) << <span class="string">"Bear"</span> <span class="comment"># -> ["Polar","Bear"]</span></span><br><span class="line">p dup_array.each { <span class="params">|element|</span> element.sub!(<span class="string">"lar"</span>, <span class="string">"oh"</span>) } <span class="comment"># -> ["Pooh", "Bear"]</span></span><br><span class="line">p array <span class="comment"># -> ["Polar"]</span></span><br></pre></td></tr></table></figure><p>使用 <code>Marshal</code> 會有相當的限制,除了時間,他要對物件序列化並還原序列化,還必須考慮記憶體的需求數量。複製的物件將會佔用它自己的記憶體空間,如此一來使得 <code>Marshal::dump</code> 建立序列化位元組資料流,因此假如載入大型物件會使得程式大幅增加記憶體需求。</p><p>另一個問題是 <code>Marshal</code> 並非可以對所有的物件作序列化,例如 IO 類別的實體、單例物件(singleton objects)等,會引發 TypeError 的例外。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><ul><li>大部分的情況我們使用 <code>dup</code> 就可以解決想要複製物件的問題了,但如果我們可以了解這些限制能讓我們將來在需要做深層副本的時候遠離麻煩。</li><li>Ruby 表現得像為不可變物件傳遞值(pass-by-value),為可變物件傳遞參考(pass-by-reference)。</li><li><code>dup</code> 和 <code>clone</code> 只會建立淺層副本。</li><li>大多數物件來說,<code>Marshal</code> 能在需要時用來建立深層副本。</li></ul><h3 id="參考:-Effective-Ruby"><a href="#參考:-Effective-Ruby" class="headerlink" title="參考: Effective Ruby"></a>參考: <a href="https://amzn.to/2KzOoVD" target="_blank" rel="noopener">Effective Ruby</a></h3>]]></content>
<tags>
<tag> Ruby </tag>
<tag> pass-by-reference </tag>
<tag> pass-by-value </tag>
</tags>
</entry>
<entry>
<title>Clean Code - 有意義的命名</title>
<link href="/2020/06/13/clean-code-naming/"/>
<url>/2020/06/13/clean-code-naming/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,相信看過 <a href="/2020/06/06/clean-code">Clean Code - 無瑕的程式碼</a> 這篇文章的朋友們已經知道什麼是 Clean Code 了,接下來就會需要探討究竟要如何實現?</p><p><a href="https://amzn.to/3kSY9ug" target="_blank" rel="noopener">無瑕的程式碼-敏捷軟體開發技巧守則 (Clean Code: A Handbook of Agile Software Craftsmanship)</a> 第二章有意義的命名,就像我之前說的最簡單最困難,相信已經身為工程師的大家們,寫個 method 是一件相當容易的事情,但寫個好的 method 名稱,似乎就不那麼容易了,本篇文章會針對書中提到的幾個重點做說明並用 Ruby 來做舉例。</p><h2 id="讓名稱代表意圖—-使之名符其實"><a href="#讓名稱代表意圖—-使之名符其實" class="headerlink" title="讓名稱代表意圖—-使之名符其實"></a>讓名稱代表意圖—-使之名符其實</h2><p>變數、方法或類別的名稱,它應該要告訴我們,它為什麼會在這出現、它要做什麼用、該如何使用它。如果一個名稱需要註解的輔助,那麼這個名稱就不具備展現意圖的能力。</p><p>當我們試著說出下面這段程式碼的目的,竟然是一件相當困難的事情,會出現我都看得懂,但不知道他要幹嘛的感覺。我想當我們閱讀程式碼出現這樣的想法的時候,其實就表示說這可能是一段命名不好的程式碼。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_them</span></span></span><br><span class="line"> list1 = []</span><br><span class="line"> the_list.each <span class="keyword">do</span> <span class="params">|x|</span></span><br><span class="line"> <span class="keyword">if</span> x[<span class="number">0</span>] == <span class="number">4</span></span><br><span class="line"> list1.push(x)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> list1</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>雖然程式碼很短很簡單,但問題不在於程式碼的簡易度,而是在於程式碼的<strong>隱含性(implicity)</strong>,即程式碼的上下文資訊(context)未能由程式本身明確地展現出來的程度,這邊突然想起<a href="https://www.youtube.com/watch?v=pFDRvya23us" target="_blank" rel="noopener">知識的詛咒</a>,就好像撰寫程式碼的工程師心裡知道一切,但是並沒有把心裡知道的寫出來。</p><p>我們把上面那段程式碼做一些修改:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_flagged_cells</span></span></span><br><span class="line"> flagged_cells = []</span><br><span class="line"> game_board.each <span class="keyword">do</span> <span class="params">|cell|</span></span><br><span class="line"> <span class="keyword">if</span> cell[STATUS_VALUE] == FLAGGED</span><br><span class="line"> flagged_cells.push(cell)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> flagged_cells</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>這邊只是將程式碼的命名做修改,看起來就不太一樣了,比前一個例子清楚一些,那我們再做一些修改:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_flagged_cells</span></span></span><br><span class="line"> flagged_cells = []</span><br><span class="line"> game_board.each <span class="keyword">do</span> <span class="params">|cell|</span></span><br><span class="line"> <span class="keyword">if</span> cell.flagged?</span><br><span class="line"> flagged_cells.push(cell)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> flagged_cells</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_flagged_cells</span></span></span><br><span class="line"> flagged_cells = []</span><br><span class="line"> game_board.each <span class="keyword">do</span> <span class="params">|cell|</span></span><br><span class="line"> <span class="keyword">next</span> <span class="keyword">unless</span> cell.flagged?</span><br><span class="line"></span><br><span class="line"> flagged_cells.push(cell)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> flagged_cells</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>大家發現了嗎?這邊看起來就是有一個遊戲版,然後我們用這個方法取得在遊戲版上被標記的格子。我們只是做重新命名,就看出程式碼的目的了!</p><p>最後還可以改成下面這樣:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_flagged_cells</span></span></span><br><span class="line"> game_board.select(&<span class="symbol">:flagged?</span>)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><h2 id="產生有意義的區別"><a href="#產生有意義的區別" class="headerlink" title="產生有意義的區別"></a>產生有意義的區別</h2><p>如果說我們發現程式碼中出現兩個名稱是看不出區別的,那就不應該同時用這兩個名稱,例如 <code>Info</code> 和 <code>Data</code> 是不可區分的無意義字詞,在沒有特別約定的情況下,<code>money_account</code> 和 <code>money</code>、<code>customer_info</code> 和 <code>customer</code>、<code>account_data</code> 和 <code>account</code>、<code>the_message</code> 和 <code>message</code> 都是沒有區別的!</p><h2 id="使用能唸出來的名稱"><a href="#使用能唸出來的名稱" class="headerlink" title="使用能唸出來的名稱"></a>使用能唸出來的名稱</h2><p>大家可以想像一下,如果工程師們在討論一個念不出來的方法,究竟要怎麼討論?</p><h2 id="使用可被搜尋的名稱"><a href="#使用可被搜尋的名稱" class="headerlink" title="使用可被搜尋的名稱"></a>使用可被搜尋的名稱</h2><p>假設某個數字在程式碼中有特別的意義,<code>WORK_DAY_PER_WEEK = 5</code>,像這樣的寫法會比直接在程式碼中用 5 去做計算來的好,因為如果我們用 <code>WORK_DAY_PER_WEEK</code> 去搜尋整個專案,絕對會比用 5 搜尋來得好!</p><h2 id="類別的命名"><a href="#類別的命名" class="headerlink" title="類別的命名"></a>類別的命名</h2><p>類別和物件應該使用名詞或名詞片語來命名,類別的名稱也不應該是動詞。大家可以參考 <a href="https://railsbook.tw/chapters/08-ruby-basic-4.html" target="_blank" rel="noopener">類別(Class)與模組(Module)</a> 這篇文章,類別其實就像是一個模子,當我們 new 出一個實體(Instance),表示為具有類別上狀態與行為的物件,這就是個名詞無誤。</p><h2 id="方法的命名"><a href="#方法的命名" class="headerlink" title="方法的命名"></a>方法的命名</h2><p>方法應該使用動詞或動詞片語來命名。方法就是個行為,行為是動作,所以應該要是動詞。</p><h2 id="每個概念使用一種字詞"><a href="#每個概念使用一種字詞" class="headerlink" title="每個概念使用一種字詞"></a>每個概念使用一種字詞</h2><p>例如 <code>fetch</code>、<code>retrieve</code>、<code>get</code> 這三個都是取得的意思,應該要統一用一種。</p><h2 id="別說雙關語"><a href="#別說雙關語" class="headerlink" title="別說雙關語"></a>別說雙關語</h2><p>避免使用同一個字詞來表示兩種不同的目的,例如專案中已經大量使用 <code>add</code> 表示相加或相連兩個現有的值,然後形成新的值,而我們今天要撰寫一個新的方法是會將單一參數放入一個集合容器中,那我們可以取名為 add 嗎?如果我們用 <code>add</code> 了是不是就表示整個專案裡的 <code>add</code> 會有兩種意思?這就是一種雙關語的表現,所以我們應該取名為 <code>insert</code> 或 <code>append</code> 之類的。</p><h2 id="使用解決方案領域的命名"><a href="#使用解決方案領域的命名" class="headerlink" title="使用解決方案領域的命名"></a>使用解決方案領域的命名</h2><p>解決方案領域其實就是工程師領域的意思,因為在寫跟在看程式碼的人都是工程師,所以如果可以應該盡量使用工程師一看就懂的字詞,例如 <code>JobQueue</code> 之類的。</p><h2 id="使用問題領域的命名"><a href="#使用問題領域的命名" class="headerlink" title="使用問題領域的命名"></a>使用問題領域的命名</h2><p>問題領域就是這個專案在做什麼領域的事情,例如如果是電商網站那就是電商的領域,如果是遊戲網站就會是遊戲的領域。如果沒有工程師熟悉的字詞可以使用的時候,或某段程式碼與問題領域的概念更接近,我們就會使用該問題領域的術語來命名。</p><h2 id="添加有意義的上下文資訊-Context"><a href="#添加有意義的上下文資訊-Context" class="headerlink" title="添加有意義的上下文資訊 (Context)"></a>添加有意義的上下文資訊 (Context)</h2><p>例如有一個變數 <code>state</code>,如果單看這個變數會看不出這是什麼,如果改成 <code>addr_state</code> 至少可以知道這是地址的州,更好的做法是將零碎的變數給予更大的類別 Address。但記得如果小的名稱就能清楚表達的話,就不需要加入其他 context。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>我想從這章節可以學到很多實際上的做法,也會讓我省思過去是不是寫了不好的命名,也確實遇過書中寫到面對不好命名的窘境,雖然說程式碼是要讓電腦去執行的,但其實也是要讓人類去閱讀的,因為撰寫程式碼的也是人類,所以能夠表達清楚,是讓後面接手的人可以更好維護的方法之一。</p>]]></content>
<tags>
<tag> Clean Code </tag>
<tag> Coding </tag>
<tag> Agile </tag>
</tags>
</entry>
<entry>
<title>Ruby 物件導向設計實踐-敏捷入門</title>
<link href="/2020/06/09/Practical-Object-Oriented-Design-in-Ruby/"/>
<url>/2020/06/09/Practical-Object-Oriented-Design-in-Ruby/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,最近在整理 medium 舊的文章 <a href="https://medium.com/@cindyliu923/ruby-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%AF%A6%E8%B8%90%E8%AE%80%E6%9B%B8%E7%AD%86%E8%A8%98-e0a357e3fd17" target="_blank" rel="noopener"><strong>Ruby 物件導向設計實踐讀書筆記</strong></a>,想把這篇也放在這裡,但覺得需要重新整理一下,<del>就跟過了一陣子再看我寫的程式碼一樣</del>,所以這篇文章就此誕生?</p><p>首先跟大家說一下,<a href="https://amzn.to/3fmWVGq" target="_blank" rel="noopener">Ruby 物件導向設計實踐-敏捷入門 (Practical Object-Oriented Design in Ruby: An Agile Primer)</a> 是一本書,中文版:<a href="https://tinyurl.com/y3z66m6r" target="_blank" rel="noopener">Ruby 物件導向設計實踐-敏捷入門</a>,作者是 <a href="https://www.sandimetz.com" target="_blank" rel="noopener">Sandi Metz</a> 致力推行物件導向程式設計的實踐,出了兩本書 Practical Object-Oriented Design (POODR) 和 <a href="https://www.sandimetz.com/99bottles" target="_blank" rel="noopener">99 Bottles of OOP</a>,都是關於物件導向設計要如何實踐,而且最近發現 Sandi Metz 有很多場精彩的演講,大家可以去看看:</p><ul><li><a href="https://www.youtube.com/watch?v=v-2yFMzxqwU" target="_blank" rel="noopener">GORUCO 2009 - SOLID Object-Oriented Design by Sandi Metz</a></li><li><a href="https://www.youtube.com/watch?v=8bZh5LMaSmE" target="_blank" rel="noopener">RailsConf 2014 - All the Little Things by Sandi Metz</a></li><li><a href="https://www.youtube.com/watch?v=OMPfEXIlTVE" target="_blank" rel="noopener">RailsConf 2015 - Nothing is Something</a></li><li><a href="https://www.youtube.com/watch?v=PJjHfa5yxlU" target="_blank" rel="noopener">RailsConf 2016 - Get a Whiff of This by Sandi Metz</a></li><li><a href="https://www.youtube.com/watch?v=mpA2F1In41w" target="_blank" rel="noopener">hafentalks #7 - Sandi Metz: “Go Ahead, Make a Mess”</a></li></ul><p><strong>接下來是我在閱讀這本書每一章節的筆記,我另外在下面用註解的方式針對我的筆記做說明</strong></p><blockquote><p>參考程式碼如果是 A 這種沒意義的命名請不要認真覺得要這樣寫唷,只是懶得想例子而已,記得要做有意義的命名。</p></blockquote><h2 id="大綱"><a href="#大綱" class="headerlink" title="大綱"></a>大綱</h2><ul><li><a href="#物件導向的設計">物件導向的設計</a></li><li><a href="#設計具有單一職責的類別">設計具有單一職責的類別</a></li><li><a href="#管理依賴關係">管理依賴關係</a></li><li><a href="#建立靈活的介面">建立靈活的介面</a></li><li><a href="#使用鴨子類型技巧降低成本">使用鴨子類型技巧降低成本</a></li><li><a href="#藉由繼承取得行為">藉由繼承取得行為</a></li><li><a href="#使用模組共用角色行為">使用模組共用角色行為</a></li><li><a href="#組合物件">組合物件</a></li><li><a href="#設計節省成本的測試">設計節省成本的測試</a></li></ul><h2 id="物件導向的設計"><a href="#物件導向的設計" class="headerlink" title="物件導向的設計"></a>物件導向的設計</h2><ul><li><p>物件導向設計與依賴關係管理相關</p><ul><li>不受管理的依賴關係很容易造成嚴重破壞,因為物件之間彼此了解太多<blockquote><p>依賴關係其實就是如果某個物件的修改會影響到另一個物件,我們就可以說這兩個物件具有依賴關係</p></blockquote></li></ul></li><li><p>設計的目的是使你日後仍然可以繼續設計</p></li><li><p>設計原則</p><ul><li><p>SOLID</p><ul><li><p>單一職責(Single Responsibility Principle, SRP)</p><blockquote><p>簡單說就是一個物件只做一件事情</p></blockquote></li><li><p>開閉原則(Open-Closed Principle, OCP)</p><blockquote><p>在設計已經完整的前提下只能增加程式碼,不能改既有的程式碼</p></blockquote></li><li><p>里氏代替原則(Liskov Substitution Principle, LSP)</p><blockquote><p>若是使用繼承,子類別實作的行為必須要與父類別或是介面所定義的行為一致,並且子類別要能夠完全取代掉父類別</p></blockquote></li><li><p>介面隔離原則(Interface Segregation Principle, ISP)</p><blockquote><p>No client should be forced to depend on methods it does not use.</p></blockquote></li><li><p>依賴倒置原則(Dependency Inversion Principle, DIP)</p><blockquote><p>簡單講是物件之間的依賴關係的處理,可參考<a href="https://notfalse.net/1/dip" target="_blank" rel="noopener">這篇文章</a>,雖然範例不是 Ruby,但我覺得說明的蠻有趣的,如果說看完文章想轉成 Ruby 的話最後結果類似下面這段程式碼,其中 <code>stuffer</code> 是抽象介面,即實際上並不存在 <code>stuffer</code> 的類別</p></blockquote></li></ul><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">People</span></span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">eat</span><span class="params">(stuffer)</span></span></span><br><span class="line"> stuffer.new.stuff</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Hamburger</span></span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">stuff</span></span></span><br><span class="line"> p <span class="string">'咔拉雞腿滿福堡 好棒棒'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Spaghetti</span></span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">stuff</span></span></span><br><span class="line"> p <span class="string">'大蒜辣椒麵 :D'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">People.new.eat(Hamburger)</span><br><span class="line">People.new.eat(Spaghetti)</span><br></pre></td></tr></table></figure></li><li><p>Don’t Repeat Yourself, DRY</p></li><li><p>Law of Demeter, LoD</p></li></ul></li><li><p>若未進行設計 => 我可以增加這項功能,但這會把所有東西破壞。</p></li></ul><h2 id="設計具有單一職責的類別"><a href="#設計具有單一職責的類別" class="headerlink" title="設計具有單一職責的類別"></a>設計具有單一職責的類別</h2><ul><li>程式碼應具備的特點(TRUE)<ul><li>透明性(Transparent)-程式碼的修改結果要顯而易見</li><li>合理性(Reasonable)-修改的成本要跟修改後的效益成正比</li><li>可用性(Usable)-既有程式碼在任何時候都要保持可用</li><li>典範性(Examplary)-程式碼本身鼓勵為延續這些特點的修改</li></ul></li><li>判斷方法<ul><li>嘗試用一句話描述類別(Class),若描述中出現<strong>和</strong>、<strong>或</strong>表示不只做一件事情</li><li>高聚合-這個類別所做的所有事情都與其目標非常相關</li></ul></li><li>依賴<strong>行為</strong>而非資料<blockquote><p>資料庫的相關書籍也有提到說應用程式跟資料應該要分離,在寫程式的時候不應該依賴資料內容才對,否則會有應用程式與資料黏在一起的感覺啊,會進入越來越難寫的窘境</p></blockquote></li><li>只負責單一事物的類別能夠將事物與應用程式的其他部分有所<strong>隔離</strong></li></ul><h2 id="管理依賴關係"><a href="#管理依賴關係" class="headerlink" title="管理依賴關係"></a>管理依賴關係</h2><ul><li>低耦合</li><li>依賴像膠水,類別和接觸到他的事物黏在一起,存在幾滴膠水是有必要的,但如果膠水太多,應用程式會凝結成堅固的一塊</li><li>依賴注入(dependency injection)<blockquote><p>用這樣的方式其實就表示說當 A 和 B 必須具有依賴關係的時候,寧願讓依賴是 B 從外面丟進去 A 裡面,也不要是包在 A 裡面不容易察覺的地方</p></blockquote><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:x</span>, <span class="symbol">:y</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(x, y)</span></span></span><br><span class="line"> @x = x</span><br><span class="line"> @y = y</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">a_method</span></span></span><br><span class="line"> x * y.b_method</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment"># B 從外面丟進去 A 裡面</span></span><br><span class="line">A.new(x, B.new(...))</span><br></pre></td></tr></table></figure></li><li>隔離依賴<ul><li>隔離實例建立(當無法使用<strong>依賴注入</strong>時)<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 第一種方式</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:x</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(x)</span></span></span><br><span class="line"> @x = x</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">a_method</span></span></span><br><span class="line"> x * b.b_method</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">b</span></span></span><br><span class="line"> @b <span class="params">||</span>= B.new(...)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment"># 第二種方式</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:x</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(x)</span></span></span><br><span class="line"> @x = x</span><br><span class="line"> @b <span class="params">||</span>= B.new(...)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">a_method</span></span></span><br><span class="line"> x * @b.b_method</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>隔離外部訊息<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">a_method</span></span></span><br><span class="line"> x * b_method</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="comment"># 將外部訊息 b_method 隔離出來(似乎可以用委派)</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">b_method</span></span></span><br><span class="line"> b.b_method</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li></ul></li><li>移除參數順序依賴<ul><li>使用 Hash<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:x</span>, <span class="symbol">:y</span>, <span class="symbol">:z</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(args)</span></span></span><br><span class="line"> @x = args[<span class="symbol">:x</span>]</span><br><span class="line"> @y = args[<span class="symbol">:y</span>]</span><br><span class="line"> @z = args[<span class="symbol">:z</span>]</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>明確定義預設值<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(args)</span></span></span><br><span class="line"> @x = args[<span class="symbol">:x</span>] <span class="params">||</span> <span class="number">5</span></span><br><span class="line"> @y = args[<span class="symbol">:y</span>] <span class="params">||</span> <span class="number">10</span></span><br><span class="line"> <span class="comment"># 如果是 boolean 下面這樣寫會有問題,全部都變成 true</span></span><br><span class="line"> @z = args[<span class="symbol">:z</span>] <span class="params">||</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment"># 可以改使用 fetch 來寫</span></span><br><span class="line"> @z = args.fetch(<span class="symbol">:z</span>, <span class="literal">true</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 merge</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(args)</span></span></span><br><span class="line"> args = defaults.merge(args)</span><br><span class="line"> @x = args[<span class="symbol">:x</span>]</span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">defaults</span></span></span><br><span class="line"> {<span class="symbol">x:</span> <span class="number">5</span>, <span class="symbol">y:</span> <span class="number">10</span>}</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>隔離多重參數初始化操作<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 當 A 是外部介面的一部分時</span></span><br><span class="line"><span class="comment"># 例如某個框架的東西,對我來說是不能修改的部分</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">SomeFramework</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:x</span>, <span class="symbol">:y</span>, <span class="symbol">:z</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(x, y, z)</span></span></span><br><span class="line"> @x = x</span><br><span class="line"> @y = y</span><br><span class="line"> @z = z</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 將外部介面包裝起來</span></span><br><span class="line"><span class="comment"># 為某個特定類別建立實例,可以稱之為 factory,</span></span><br><span class="line"><span class="comment"># 當被迫無法修改外部介面時可以使用的技巧</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">AWrapper</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">self</span>.<span class="title">a</span><span class="params">(args)</span></span></span><br><span class="line"> SomeFramework::A.new(args[<span class="symbol">:x</span>], args[<span class="symbol">:y</span>], arg[<span class="symbol">:z</span>])</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li></ul></li><li>選擇依賴方向<ul><li>告訴類別他們要依賴那些變化少於他們自身的事物<ul><li>有些類別更容易發生變化</li><li>具體類別比抽象類別更容易發生變化</li><li>修改具有許多依賴關係的類別會造成廣泛的影響<blockquote><p>這邊可以參考上面提到的 SOLID 的 <strong>依賴倒置原則(Dependency Inversion Principle, DIP)</strong></p></blockquote></li></ul></li></ul></li></ul><h2 id="建立靈活的介面"><a href="#建立靈活的介面" class="headerlink" title="建立靈活的介面"></a>建立靈活的介面</h2><ul><li>定義介面<ul><li>公共介面<ul><li>顯露出主要職責</li><li>期望被其他物件呼叫</li><li>不會隨便改變</li><li>其他物件可以放心依賴它</li><li>在測試裡被詳盡記錄</li></ul></li><li>私有介面<ul><li>要處理實作細節</li><li>不希望被傳送到其它物件</li><li>可因任何原因變化</li><li>其他物件不能放心依賴它</li><li>可能不會在測試裡被引用</li></ul></li></ul></li><li>關鍵字(Ruby 裡的方法)<ul><li>public</li><li>protected</li><li>private</li><li>可參考資料:<a href="https://kaochenlong.com/2011/07/26/public-protected-and-private-method-in-ruby" target="_blank" rel="noopener">Public, Protected and Private Method in Ruby</a></li></ul></li><li><strong>詢問傳送方想要什麼</strong>而非<strong>告訴接收者如何表現</strong><ul><li>表示物件之間彼此信任</li></ul></li><li>Law of Demeter, LoD<ul><li>對物件之間的傳遞進行限制:禁止將一則訊息藉由第二個不同的物件轉發給第三個物件,即<strong>只能與你的鄰近對話</strong>或<strong>只能使用一個小圓點</strong></li><li>小心使用委派(delegate) - Ruby 的 <code>delegate.rb</code>、<code>forwardable.rb</code>,Rails 的 <code>delegate</code> 方法</li></ul></li></ul><h2 id="使用鴨子類型技巧降低成本"><a href="#使用鴨子類型技巧降低成本" class="headerlink" title="使用鴨子類型技巧降低成本"></a>使用鴨子類型技巧降低成本</h2><ul><li>鴨子類型(duck typing)<ul><li>多態性(polymorphism):許多不同物件回應相同訊息的能力,duck typing 是實作多態性的方法之一<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">prepare</span><span class="params">(preparers)</span></span></span><br><span class="line"> preparers.each <span class="keyword">do</span> <span class="params">|preparer|</span></span><br><span class="line"> preparer.prepare_something(<span class="keyword">self</span>)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment"># 特定的 type</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">prepare_something</span><span class="params">(a)</span></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment"># 特定的 type</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">prepare_something</span><span class="params">(a)</span></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h2 id="藉由繼承取得行為"><a href="#藉由繼承取得行為" class="headerlink" title="藉由繼承取得行為"></a>藉由繼承取得行為</h2><ul><li>classical inheritance<ul><li>繼承的核心是一種用於實作<strong>訊息自動委派</strong>的機制</li></ul></li><li>如果程式碼中的傳送者可以說話,如果說出:<strong>我知道你是誰,因為我知道你會做什麼</strong>,這項知識是一種會增加修改成本的依賴關係</li><li>建立抽象父類別<blockquote><p>這裡會寫說抽象是因為實際上我們不會去 new 一個父類別出來使用,而是會針對不同的情況 new 出不一樣的子類別,所以我們說是將行為提升至抽象,而子類別我們就會說是具體的</p></blockquote><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildernB</span> < Father</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>提升抽象行為<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:share</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @share = arg[<span class="symbol">:share</span>]</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:specific</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @specific = arg[<span class="symbol">:specific</span>]</span><br><span class="line"> <span class="keyword">super</span>(args) <span class="comment"># 子類別現在"必須"傳送 super</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>從具體分離出抽象<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:share</span>, <span class="symbol">:method_arg1</span>, <span class="symbol">:method_arg2</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @share = arg[<span class="symbol">:share</span>]</span><br><span class="line"> <span class="comment"># 本來兩個子類別方法中共用的參數</span></span><br><span class="line"> <span class="comment"># 從具體的子類別中分離出來</span></span><br><span class="line"> @method_arg1 = arg[<span class="symbol">:method_arg1</span>]</span><br><span class="line"> @method_arg2 = arg[<span class="symbol">:method_arg2</span>]</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>使用範本方法模式(<strong>template method pattern</strong>)<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:share</span>, <span class="symbol">:method_arg1</span>, <span class="symbol">:method_arg2</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @share = arg[<span class="symbol">:share</span>]</span><br><span class="line"> @method_arg1 = arg[<span class="symbol">:method_arg1</span>] <span class="params">||</span> default_arg1</span><br><span class="line"> @method_arg2 = arg[<span class="symbol">:method_arg2</span>] <span class="params">||</span> default_arg2</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 共同的預設值</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">default_arg1</span></span></span><br><span class="line"> <span class="string">'10-arg1'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 子類別的預設值</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">default_arg2</span></span></span><br><span class="line"> <span class="string">'23'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildernB</span> < Father</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 子類別的預設值</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">default_arg2</span></span></span><br><span class="line"> <span class="string">'2.1'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>實作所有的範本方法(<strong>template method</strong>)<ul><li>將子類別的方法寫進父類別中,即使是不做事也要實作該方法,讓工程師知道繼承這個類別時一定要實作哪些方法<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 只要在當下稍微用心一點,</span></span><br><span class="line"> <span class="comment"># 建立出在失敗時帶有合理錯誤訊息的程式碼,</span></span><br><span class="line"> <span class="comment"># 就能夠得到永久性的益處</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">default_arg2</span></span></span><br><span class="line"> raise NotImplementedError,</span><br><span class="line"> <span class="string">"This <span class="subst">#{<span class="keyword">self</span><span class="class">.<span class="keyword">class</span>} <span class="title">cannot</span> <span class="title">respond</span> <span class="title">to</span>:"</span></span></span></span><br><span class="line"><span class="string"><span class="subst"> <span class="keyword">end</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="keyword">end</span></span></span></span><br></pre></td></tr></table></figure></li></ul></li><li>父子間的耦合管理<ul><li>緊密耦合的類別會黏再一起,並且可能無法單獨修改<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:specific</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @specific = arg[<span class="symbol">:specific</span>]</span><br><span class="line"> <span class="keyword">super</span>(args) <span class="comment"># 子類別現在"必須"傳送 super</span></span><br><span class="line"> <span class="comment"># 這個 super 造成父子之間的耦合</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"># 並且需要所有子類別在完全相同的地方傳送 super</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 父類別有一樣的方法</span></span><br><span class="line"> <span class="comment"># 同上形成耦合</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">other</span></span></span><br><span class="line"> <span class="keyword">super</span>.merge(hash)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>使用鉤子(hook)訊息解耦<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> <span class="keyword">attr_reader</span> <span class="symbol">:share</span>, <span class="symbol">:method_arg1</span>, <span class="symbol">:method_arg2</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(**args)</span></span></span><br><span class="line"> @share = arg[<span class="symbol">:share</span>]</span><br><span class="line"> @method_arg1 = arg[<span class="symbol">:method_arg1</span>] <span class="params">||</span> default_arg1</span><br><span class="line"> @method_arg2 = arg[<span class="symbol">:method_arg2</span>] <span class="params">||</span> default_arg2</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 提供子類別使用</span></span><br><span class="line"> post_initialize(args)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 實作方法,但不做事</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">post_initialize</span><span class="params">(args)</span></span></span><br><span class="line"> <span class="literal">nil</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 子類別可選擇性覆蓋這個方法</span></span><br><span class="line"> <span class="comment"># 子類別不再控制初始化</span></span><br><span class="line"> <span class="comment"># 將特殊化提供給更大型的抽象演算法</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">post_initialize</span></span></span><br><span class="line"> @specific = arg[<span class="symbol">:specific</span>]</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">other</span></span></span><br><span class="line"> {<span class="symbol">a:</span> <span class="string">'a'</span>, <span class="symbol">b:</span> <span class="string">'b'</span>}.merge(local_other)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 用於子類別覆蓋的 hook</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">local_other</span></span></span><br><span class="line"> {}</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildrenA</span> < Father</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 不用強迫子類別知道父類別實作了 other 的方法</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">local_other</span></span></span><br><span class="line"> {<span class="symbol">c:</span> <span class="string">'c'</span>}</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li>使用 hook 方法可以讓繼承者不用強迫傳送 super,並且還能提供特殊化內容</li></ul></li></ul><h2 id="使用模組共用角色行為"><a href="#使用模組共用角色行為" class="headerlink" title="使用模組共用角色行為"></a>使用模組共用角色行為</h2><ul><li>理解物件所扮演的角色,找出隱藏角色,建立程式碼,以便在多個扮演者之間共用行為,同時要最小化其中所產生的依賴關係</li><li>ruby 的模組(module)<ul><li>撰寫技巧與繼承相似,但模組更在乎的是<strong>像什麼</strong>,而繼承是<strong>是什麼</strong><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># https://github.com/skmetz/poodr2/blob/master/7_10.rb</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">Schedulable</span></span></span><br><span class="line"> <span class="keyword">attr_writer</span> <span class="symbol">:schedule</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">schedule</span></span></span><br><span class="line"> @schedule <span class="params">||</span>= Schedule.new</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">schedulable?</span><span class="params">(starting, ending)</span></span></span><br><span class="line"> !scheduled?(starting - lead_days, ending)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">scheduled?</span><span class="params">(starting, ending)</span></span></span><br><span class="line"> schedule.scheduled?(<span class="keyword">self</span>, starting, ending)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 包含者可以加以覆蓋</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">lead_days</span></span></span><br><span class="line"> <span class="number">0</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li></ul></li><li>找尋方法的順序<ul><li>單例類別(Singleton class 只在這個 instance 所定義的方法)->模組(extend instance 的 module 所定義的方法)->類別->類別包含的模組->父類別->父類別包含的模組->Object …</li><li><a href="https://www.spreered.com/ruby-object-model-1" target="_blank" rel="noopener">Ruby 的繼承鍊 (1) - 如何實踐物件導向</a></li><li><a href="https://www.spreered.com/ruby-object-model-include-prepend-extend" target="_blank" rel="noopener">Ruby 的繼承鍊 (2) - Module 的 include、prepend 和 extend</a></li></ul></li><li><strong>抽象父類別裡的所有程式碼都應該適用於每個繼承他的類別</strong>,父類別不應該包含只適用於部分(而非全部)子類別的程式碼,這項限制也同樣應用在模組上:<strong>模組裡的程式碼必須也能夠一併適用於包含他的所有事物</strong></li><li>里氏代替原則(Liskov Substitution Principle, LSP):子類型必須能夠代替他們的父類型</li></ul><h2 id="組合物件"><a href="#組合物件" class="headerlink" title="組合物件"></a>組合物件</h2><ul><li>composition<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">require</span> <span class="string">'forwardable'</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parts</span></span></span><br><span class="line"> extend Forwardable</span><br><span class="line"> def_delegators <span class="symbol">:</span>@parts, <span class="symbol">:size</span>, <span class="symbol">:each</span></span><br><span class="line"> <span class="keyword">include</span> Enumerable</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(parts)</span></span></span><br><span class="line"> @parts = parts</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">spares</span></span></span><br><span class="line"> select {<span class="params">|part|</span> part.needs_spare}</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Struct 接收的是按順序排列的初始化參數,</span></span><br><span class="line"><span class="comment"># 而 OpenStruct 在初始化時則是接收一個 Hash</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'ostruct'</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">PartsFactory</span></span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">self</span>.<span class="title">build</span><span class="params">(config, parts_class = Parts)</span></span></span><br><span class="line"> parts_class.new(</span><br><span class="line"> config.collect {<span class="params">|part_config|</span></span><br><span class="line"> create_part(part_config)})</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">self</span>.<span class="title">create_part</span><span class="params">(part_config)</span></span></span><br><span class="line"> OpenStruct.new(</span><br><span class="line"> <span class="symbol">name:</span> part_config[<span class="number">0</span>],</span><br><span class="line"> <span class="symbol">description:</span> part_config[<span class="number">1</span>],</span><br><span class="line"> <span class="symbol">needs_spare:</span> part_config.fetch(<span class="number">2</span>, <span class="literal">true</span>))</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># https://github.com/skmetz/poodr/blob/master/chapter_8.rb#L422</span></span><br></pre></td></tr></table></figure></li><li>組合允許物件之間的結構獨立性,其代價是需要明確進行訊息委派</li><li>如果問題可以使用組合技巧解決,應該盡可能使用組合,如果無法明確保證繼承是一種更好的解決方案,要用組合,因為組合的依賴關係比繼承少許多</li><li>選擇關係:<ul><li>將繼承用於<strong>是什麼</strong>的關係</li><li>將 duck typing 用於<strong>表現得像什麼</strong>的關係<ul><li>思考角色最明確的方法是從外部,以角色扮演者的持有者作為觀點</li></ul></li><li>將組合用於<strong>含有什麼</strong>的關係</li></ul></li></ul><h2 id="設計節省成本的測試"><a href="#設計節省成本的測試" class="headerlink" title="設計節省成本的測試"></a>設計節省成本的測試</h2><ul><li>測試的意圖<ul><li>找出錯誤</li><li>提供文件<ul><li>抱著假設自己將來會得健忘症一樣來撰寫測試</li></ul></li><li>延後設計決定</li><li>支持抽象<ul><li>除非程式碼有測試,否則會出現一層幾乎無法安全做出任何修改的設計抽象</li></ul></li><li>暴露出設計缺陷<ul><li>如果一項測試需要麻煩的設定,就表示程式碼期望過多的上下文</li><li>如果測試某個物件會將一大堆的其他物件捲進來,這表示程式碼有著大量的依賴關係</li><li>如果測試難以撰寫,那麼其他物件也將會發現這段程式碼難以重複使用</li></ul></li></ul></li><li>測試的內容<ul><li>所有事物只測試一次,並且要在適當的地方進行</li><li>將每個物件當成一個黑盒子</li><li>針對定義在公共介面的訊息撰寫測試</li><li>輸入訊息應該測試其傳回狀態,輸出的命令訊息(command)應該測試是否被傳送(行為測試),而輸出的查詢訊息(query)則不應該被測試。</li></ul></li><li>測試的方法<ul><li>由外向內的 BDD</li><li>由內向外的 TDD</li></ul></li><li>不要測試沒有依賴關係的輸入訊息,而是刪除它:刪除未使用的程式碼能夠立即節省成本,保留未使用的程式碼比刪除之後再恢復他們所花費的成本更高</li></ul>]]></content>
<tags>
<tag> Agile </tag>
<tag> Ruby </tag>
<tag> Design </tag>
<tag> Object-Oriented </tag>
<tag> SOLID </tag>
<tag> OOP </tag>
</tags>
</entry>
<entry>
<title>Clean Code - 無瑕的程式碼</title>
<link href="/2020/06/06/clean-code/"/>
<url>/2020/06/06/clean-code/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,想跟大家分享最近在閱讀的書:<a href="https://amzn.to/3kSY9ug" target="_blank" rel="noopener">無瑕的程式碼-敏捷軟體開發技巧守則 (Clean Code: A Handbook of Agile Software Craftsmanship)</a>,作者是 <a href="https://github.com/unclebob" target="_blank" rel="noopener">Robert C. Martin (人稱 Uncle Bob)</a>,是一本對程式設計具代表性的書。預計之後會邊看邊將每章閱讀過後的重點及心得整理成一篇文章分享給大家。</p><h2 id="為什麼要閱讀-Clean-Code?"><a href="#為什麼要閱讀-Clean-Code?" class="headerlink" title="為什麼要閱讀 Clean Code?"></a>為什麼要閱讀 Clean Code?</h2><p>想先跟大家分享我想看這本書的原因,主要是因為在工作中曾經被同事唸過我寫了奇怪的程式碼,剛開始工作在開發時我會有 Cindy 的 100 種寫法(這邊的 100 是誇飾),總之我會寫了之後又改掉、又再重寫、又再改掉、再重寫,始終不清楚怎麼寫才是好的寫法,畢竟要達到功能的實現,本來就有很多的方式,大概就是條條道路通羅馬的概念。對於 Clean Code,我自己目前的想法是<strong>最簡單最困難</strong>,如果說專案的程式碼可以非常清楚的表達目的,即看起來很簡單,讓接手專案的人都可以快速地上手,是我心中的 Clean Code,但其實想要達到如此境界確實是不容易的一件事情,尤其是當專案的商業邏輯相當複雜的時候,一不小心就會被牽著走,所以希望藉由這本書,提升自己的能力,為了成為更好的程式設計師。</p><h2 id="無瑕的程式碼"><a href="#無瑕的程式碼" class="headerlink" title="無瑕的程式碼"></a>無瑕的程式碼</h2><p>Uncle Bob 在書中第一章節首先對無瑕的程式碼進行說明,表示程式碼將一直存在,所以無可避免我們始終會面對現有的程式碼,而如果我們必須面對劣質的程式碼,那麼開發時間的上升絕對是必然的,因為我們必須花更多的時間去理解雜亂的程式碼。作者提出了一個關於<strong>態度</strong>的觀點,表示保護程式碼是我們的工作,即使我們被開發時程推著走的時候也應該要提出程式現況有多少的風險,這些風險是否會讓未來的開發付出更大的代價,表現出我們的專業。</p><h2 id="最根本的難題"><a href="#最根本的難題" class="headerlink" title="最根本的難題"></a>最根本的難題</h2><p>讓開發速度變快的<strong>唯一</strong>方法是隨時隨地都確保程式碼整齊潔淨。這邊我想如果要做到的話,其實邊開發邊檢視是否需要重構是必要的,因為也許在新的需求進來的時候,程式應該要如何設計的面貌會更清晰,所以作者曾經在 <a href="https://www.youtube.com/watch?v=2dKZ-dWaCiU&t=3616" target="_blank" rel="noopener">ITkonekt 2019</a> 的演講說過我們如果可以延遲應該盡量先延遲某些決定。</p><h2 id="什麼是-Clean-Code?"><a href="#什麼是-Clean-Code?" class="headerlink" title="什麼是 Clean Code?"></a>什麼是 Clean Code?</h2><p>這邊作者認為每個人對於 Clean Code 會有不同的看法,所以列了一些他請教不同資深程式設計師的說法,其中 Bjarne Stroustrup (<a href="https://amzn.to/2J4K9QM" target="_blank" rel="noopener">The C++ Programming Language</a> 的作者) 表示 Clean Code 對細節相當在意,如:<a href="https://zh.wikipedia.org/zh-tw/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F" target="_blank" rel="noopener">記憶體流失(memory leak)</a>、<a href="https://zh.wikipedia.org/zh-tw/%E7%AB%B6%E7%88%AD%E5%8D%B1%E5%AE%B3" target="_blank" rel="noopener">競爭情況(race condition)</a>、不一致的命名方式(下一篇文章會介紹)……等等。Grady Booch (<a href="https://amzn.to/3nQ0d8r" target="_blank" rel="noopener">Object-Oriented Analysis and Design with Applications</a> 的作者) 指出我們撰寫的程式碼應該說明事實,不該使人臆測。這邊我指出幾個印象深刻的,想看更多的大家可以去買書看看唷。</p><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>作者表示我們可以把此書當作是<strong>整潔程式碼之物件學派</strong>,可以學到如何寫出整潔又專業的程式碼,但不要當作這些絕對是<strong>對的</strong>,還有其他不同學派值得我們學習。</p><p>最後下面兩點指出我們要寫 Clean Code 的原因:</p><ul><li>當我們要撰寫程式碼前,其實花了不少功夫在不斷的了解舊的程式碼。</li><li>你今天寫程式的難易度,取決於周遭程式碼的可讀性高低。</li></ul><p>下一篇會介紹有意義的命名,敬請期待。</p>]]></content>
<tags>
<tag> Clean Code </tag>
<tag> Coding </tag>
<tag> Agile </tag>
</tags>
</entry>
<entry>
<title>資料庫 Transaction(交易)</title>
<link href="/2020/05/31/Database-Transaction/"/>
<url>/2020/05/31/Database-Transaction/</url>
<content type="html"><![CDATA[<p>大家好,我是 Cindy,非資訊科系相關背景的工程師,對於資料庫始終有許多的不理解,今天想跟大家分享我在網路上找到適合非本科系的同學們看的資料,以及分享一些我對於資料庫 <a href="https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1" target="_blank" rel="noopener">Ttansaction</a> 的理解。</p><h2 id="網路上的相關資料"><a href="#網路上的相關資料" class="headerlink" title="網路上的相關資料"></a>網路上的相關資料</h2><ul><li><p><a href="http://debussy.im.nuu.edu.tw/sjchen/DataBaseMan_Final.html" target="_blank" rel="noopener">資料庫系統管理課程</a><br>關於資料庫 Transaction(交易) 可以看 <strong>Course 8. 交易處理</strong> 和 <strong>Course 9. 並行控制與回復</strong>,裡面有 <strong>杰哥數位教室</strong> youtube 課程可以搭配講義學習,雖然 youtube 音質不是很好,但整體看下來對於沒有上過資料庫課程的工程師,我覺得對於觀念的理解會蠻有幫助的。</p></li><li><p><a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html" target="_blank" rel="noopener">MySQL 文件</a></p></li><li><p><a href="https://docs.postgresql.tw/tutorial/advanced-features/transactions" target="_blank" rel="noopener">PostgreSQL 文件</a></p></li><li><p><a href="https://docs.postgresql.tw/the-sql-language/concurrency-control/transaction-isolation" target="_blank" rel="noopener">PostgreSQL 文件 - isolation</a><br>MySQL 和 PostgreSQL 算是目前常用到的資料庫系統,如果在實務上有需要了解的時候,直接看文件會最快,因為各個資料庫系統實作的演算法不同。</p></li></ul><h2 id="什麼是-Transaction"><a href="#什麼是-Transaction" class="headerlink" title="什麼是 Transaction"></a>什麼是 Transaction</h2><p>如果有這樣的情境:某個功能需要對資料庫進行操作,且是對一或多筆資料進行操作,如果中間發生失敗,是不會允許有些資料變更成功,有些資料變更失敗的話,就會需要 Transaction。</p><h3 id="MySQL-關於-Transaction-的描述"><a href="#MySQL-關於-Transaction-的描述" class="headerlink" title="MySQL 關於 Transaction 的描述"></a>MySQL 關於 Transaction 的描述</h3><blockquote><p>Transactions are atomic units of work that can be <strong>committed</strong> or <strong>rolled back</strong>. When a transaction makes multiple changes to the database, either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back.</p><p>Database transactions, as implemented by InnoDB, have properties that are collectively known by the acronym <strong>ACID</strong>, for atomicity, consistency, isolation, and durability.</p><p>See Also ACID, commit, <strong>isolation level</strong>, <strong>lock</strong>, rollback.</p></blockquote><h3 id="PostgreSQL-關於-Transaction-的描述"><a href="#PostgreSQL-關於-Transaction-的描述" class="headerlink" title="PostgreSQL 關於 Transaction 的描述"></a>PostgreSQL 關於 Transaction 的描述</h3><blockquote><p>Transactions are a fundamental concept of all database systems. The essential point of a transaction is that it bundles multiple steps into a single, <strong>all-or-nothing</strong> operation. The intermediate states between the steps are not visible to other concurrent transactions, and if some failure occurs that prevents the transaction from completing, then none of the steps affect the database at all.</p></blockquote><p>由以上內容可以看到關鍵字 <strong>committed</strong> or <strong>rolled back</strong> ,表示的是 Transaction 的兩種情況:</p><ul><li>成功:committed</li><li>失敗:rolled back</li></ul><p>其實也就是 <strong>all-or-nothing</strong> 的表示,要不是全部成功(all)就是全部失敗(nothing)。<br>最常見的例子就是轉帳,假如小明要轉帳給小美,我們不會希望有轉一半的情況發生,例如小明成功扣了轉帳出去的錢,而小美卻沒有得到小明轉進帳戶的錢,所以我們就會需要 Transaction 來幫助我們做到 <strong>all-or-nothing</strong>。<br>=> 詳細參考資料:<a href="http://debussy.im.nuu.edu.tw/sjchen/Database/Final/Ch09.pdf" target="_blank" rel="noopener">交易管理</a></p><h2 id="Transaction-的四大特性-ACID"><a href="#Transaction-的四大特性-ACID" class="headerlink" title="Transaction 的四大特性 ACID"></a>Transaction 的四大特性 ACID</h2><ul><li>單元性 (<strong>Atomicity</strong>;基元性):<ul><li>交易是一個不可再分割的完整個體,它不是全部執行,就是全部不執行。</li><li>確保單元性是回復 (<strong>Recovery</strong>) 的責任。</li></ul></li><li>一致性 (<strong>Consistency</strong>):<ul><li>如果交易是全部執行,能讓資料庫從某個一致狀態,轉變到另一個一致狀態。我們則稱此次交易具有一致性。</li><li>確保一致性通常是 DBMS 程式設計師的責任。</li></ul></li><li>孤立性 (<strong>Isolation</strong>):<ul><li>某交易執行期間所用的資料或中間結果,不容許其它交易讀取或寫入,直到此交易被確認 (Commit,即:成功結束) 為止。也就是說,它不應被同時執行的其它交易所干擾。</li><li>確保孤立性是並行控制 (<strong>Concurrency Control</strong>) 的責任。可依需求定立不同層級的限制。</li></ul></li><li>永久性 (<strong>Durability</strong>, Permanency):<ul><li>一旦交易全部執行,且經過確認 (Commit) 後,其對資料庫所做的變更則永遠有效,即使未來系統當機或毀損。</li><li>一般是以備份(Back Up)、硬碟映射(Disk Mirroring)、系統日誌(System Log、System Journal)等數種方式來達成。</li><li>永久性是回復 (<strong>Recovery</strong>) 的責任。</li></ul></li></ul><p>由上面敘述就可以知道,實現 Transaction 最重要的兩件事情就是:</p><ol><li><strong>失敗回復、復原 (Failure Recovery)</strong></li><li><strong>並行控制 (Concurrency Control)</strong><br>=> 詳細參考資料:<a href="http://debussy.im.nuu.edu.tw/sjchen/Database/Final/Ch10.pdf" target="_blank" rel="noopener">並行控制與回復</a></li></ol><h2 id="SQL-標準中定義了四種數據庫的隔離級別"><a href="#SQL-標準中定義了四種數據庫的隔離級別" class="headerlink" title="SQL 標準中定義了四種數據庫的隔離級別"></a>SQL 標準中定義了四種數據庫的隔離級別</h2><p><img src="https://i.imgur.com/JmoBqZz.png" alt=""></p><blockquote><p>圖片來自 <a href="https://docs.postgresql.tw/the-sql-language/concurrency-control/transaction-isolation" target="_blank" rel="noopener">PostgreSQL 文件</a></p><ul><li>RAED UNCOMMITED:使用查詢語句不會加鎖,可能會讀到未提交的資料(Dirty Read)</li><li>READ COMMITED:只對記錄加記錄鎖,而不會在記錄之間加間隙鎖,所以允許新的記錄插入到被鎖定記錄的附近,所以再多次使用查詢語句時,可能得到不同的結果(Non-Repeatable Read)</li><li>REPEATABLE READ:多次讀取同一範圍的數據會返回第一次查詢的快照,不會返回不同的數據行,但是可能發生幻讀(Phantom Read)</li><li>SERIALIZABLE:InnoDB 隱式地將全部的查詢語句加上共享鎖,解決了幻讀的問題</li></ul></blockquote><p>p.s. MySQL 的 InnoDB 預設的是 Repeatable Read<br>p.s. PostgreSQL 預設的是 Read Committed</p><h2 id="其他可參考資料:"><a href="#其他可參考資料:" class="headerlink" title="其他可參考資料:"></a>其他可參考資料:</h2><ul><li><a href="https://blog.fntsr.tw/articles/904/#fn-904-START%20TRANSACTION" target="_blank" rel="noopener">PostgreSQL 與 MySQL 關於 transaction 的設計理念</a></li><li><a href="https://kkc.github.io/2017/10/08/transaction-note" target="_blank" rel="noopener">Transaction 筆記</a></li><li><a href="https://vladmihalcea.com/a-beginners-guide-to-acid-and-database-transactions" target="_blank" rel="noopener">A beginner’s guide to ACID and database transactions</a></li><li><a href="https://www.guru99.com/dbms-transaction-management.html" target="_blank" rel="noopener">DBMS Transaction Management: ACID Properties, Schedule</a></li><li><a href="https://draveness.me/mysql-transaction" target="_blank" rel="noopener">『浅入深出』MySQL 中事务的实现</a></li><li><a href="http://coding-geek.com/how-databases-work" target="_blank" rel="noopener">How does a relational database work</a></li><li><a href="https://vinta.ws/code/locking-and-mvcc-in-mysql-innodb.html" target="_blank" rel="noopener">Locking and MVCC in MySQL InnoDB 鎖機制與多版本並發控制</a></li></ul>]]></content>
<tags>
<tag> MySQL </tag>
<tag> PostgreSQL </tag>
<tag> Database </tag>
<tag> Transaction </tag>
<tag> SQL </tag>
</tags>
</entry>
<entry>
<title>新部落格開張</title>
<link href="/2020/05/28/first-post/"/>
<url>/2020/05/28/first-post/</url>
<content type="html"><![CDATA[<p>大家好,我決定要把讀書的筆記寫在這個部落格了,敬請期待?</p>]]></content>
</entry>
</search>