WIP 僕も爆速デプロイしたい【備忘録】
はじめに
AIが普及した今、みんなアイデアがあったら爆速で作ってデプロイしてていいなぁ〜って思ったので僕もやってみる。
作りたいのは「友達と暇つぶしするゲームアプリ」
友達とこれ欲しいね〜って旅行でなったので。
調べてみて方針決め
- Claude Code : ぼく持ってる
- Cursor : 僕持ってる
- Vercel : たぶんデプロイ先はこれ、みんなこれでやってるイメージ
- Next : じゃあ使うフレームワークはこれなはず。本当はhonoとか使いたいけど、参考文献とかを考えてNextにしようかなって。
なんかVercelだとこれができるらしい、すげぇな。
- Githubと連携して自動ビルドデプロイ
- PRごとにプレビュー環境が生成される
- セットアップが簡単
ほう、これで簡単にできるとな
参考にやってみよう。
Nextのサンプルをデプロイしてみる
アプリ名は「tamariba (溜まり場)」にしよう。
$ npx create-next-app@latest ✔ Need to install the following packages: create-next-app@15.5.2 Ok to proceed? (y) y ✔ What is your project named? … tamariba ... Success!
作ったらGithubの適当なレポジトリにアップ、プライベートレポジトリでいいかな。
そしたら、Vercelにログインして、
- New project
- [Import Git Repository] で自分のアカウントを選択
- (めんどくさいので)All repositoriesにread権限あげちゃう
- [Import Git Repository] にtamaribaが出てくるので、[import]を選択
- なんも設定変えず、[Deploy]
お、すげぇ上がった。
https://tamariba-flame.vercel.app/
これで、mainにcommitされたら自動でvercelでdeployされるらしい、すごいね。
tamaribaを作る
やりたいことをまとめないとね、Claude Codeくんと壁打ちしながら決めてこ〜
その他
カラーはこんな感じのイメージにしようかな
ゲーム案
- インディアンポーカー
- NGワードゲーム
wip
Reactデザインパターンを読んで【備忘録】
- はじめに
- HOCパターン
- Providerパターン
- PresentationalとContainerコンポーネントパターン
- ReactHooksデザインパターン
- Compound Componentパターン
- Render Props パターン
- State Reducer パターン
- Controlled Components パターン
- Extensible Styles パターン
- State Initializer パターン
はじめに
読んだのはこれ
HOCパターン
高階コンポーネントのこと。React18以降だと代わりにカスタムフックを使用することが推奨されてます。 んーけど、throw ErrorのパターンでHOCを使わないとできないパターンがありそうなんだよな。
Providerパターン
Context, Providerを利用するパターンね。バケツリレーをなくすやつだけど、React18以降では重要になっているらしいね。
PresentationalとContainerコンポーネントパターン
あー動作と見た目を分けようねってパターンか
あーそして、Containerがやっていた処理を全てcustom hooksにやらせようって話ね。まぁーー、一理ある。
ReactHooksデザインパターン
あ?hooks多用しろってこと?
Compound Componentパターン
これすごいわ。新しい発想をもらった感じがする。 タブコンポーネントとか、テーブルコンポーネントとか、複数のオブジェクトを必要とするコンポーネントの場合、オブジェクト間でのデータの受け渡しは利用側に設定させたくない。 そういう時にコンテキストとかを用いて、内部でいい感じに隠してくれる。
Render Props パターン
データを取得するのをhooksで別で分離するってイメージ
State Reducer パターン
reducerをもっと使ってもいいんじゃない?っていう提唱と、CustomReducerの書き方を示してくださった。
Controlled Components パターン
formタグでバリデーションとか送信の制御とかできるけど、それはUnControlledComponentらしい。
完全にReactでフォームの処理をコントロールしてまいましょ、っていう思想。
そこらへんの有名どころはこれね
Extensible Styles パターン
JSでCSSを制御しようっていう。tailwin とかだね。
State Initializer パターン
外部から状態を制御できるようにしてもいいよねっていう
Rustを触ってみた③【備忘録】
- 4. Understanding Ownership
こちらの続きです。
4. Understanding Ownership
Ownership(所有権)という機能についてです。メモリ管理のためのRustのユニークな機能です。
borrowing, slices, メモリ上のデータ配置について説明する章らしい。
4.1. What Is Ownership?
Rustはメモリ管理が特殊です。「GCを使って定期的に未使用のメモリをクリアする」「明示的にメモリの割り当て、解放を行う」とは違う第三の方法をとっており、それがOwnershipです。
The Stack and the Heap
スタックとヒープはどちらもメモリ構造について。
- Stack: LIFO。データは固定サイズ。
- Heap: 順番はない、ただの山積み。容量を用意して、空いてたらそこに入れていく。
StackはHeapよりも割り当てが高速。Stackはすでにある場所に入れていくだけなので。Heapは空き容量を探さないといけない。
StackはHeapよりもアクセスが高速。どうやらStackの方がメモリ的に近い場所にあるらしい。
...え?Heapなんでつかうん?w
コードが関数を呼び出すと、関数に渡された値とローカル関数がスタックにpushされる。実行されるとpopする。
これあれか、プログラム意味論で学んだ考え方だな。Operational Semantics。
Ownership Rules
- 各値にはownerがいる
- ownerは一度に一人しかいない
- ownerがスコープ外に出ると、その値はなくなる
Variable Scope
スコープは、他のプログラミングの概念と同じで大丈夫。こんな感じ。
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
}
The String Type
ownerという観点からStringを見てみる。stringリテラルとStringは違うらしい。
let sl = "hello"; // リテラル let so = String::from("hello"); // String型
そして後者は可変化することができるらしい
let mut s = String::from("hello"); s.push_str(", world!"); // push_str()関数は、リテラルをStringに付け加える println!("{}", s); // これは`hello, world!`と出力する
メモリを扱う方法がどうやら違うらしい。
Memory and Allocation
文字列リテラルの場合はコンパイル時に容量がわかるので高速。実行時にしか容量がわからないものは、String型で使える。
メモリの確保と解放は1対1に対応
Variables and Data Interacting with Move
以下の2つが異なる意味になるらしい。
let x = 5; let y = x;
let s1 = String::from("hello"); let s2 = s1;
上の場合はどちらもStackに積まれるけど、下の場合はポインタの参照が同じになるらしい。
何だけど、ポインタの参照を同じにしていると、解放する時に二重解放になっちゃうかもしれないらしくて、そのためにRustでは受け渡しが終わった段階で、元の変数は使えなくなる。これを move という。

Variables and Data Interacting with Clone
もし、以下の画像のようなことをしたかったら、cloneを使いましょう。

let s1 = String::from("hello"); let s2 = s1.clone();
Stack-Only Data: Copy
じゃあこれはmoveしないの?しないらしい。は?
let x = 5; let y = x;
これは、「コンパイル時にわかるサイズを持つデータはコピーしても高速だから、別にmoveする必要がなくcloneのような挙動でいいよね」っていう考えらしい。
そして、moveが起こるかどうかは Copy traitというアノテーションがあるかどうかで判定される。これがあれば、代入後も古い変数が使えるよ。
integer, floating-point, boolean, characterはCopy traitがあるよ。
tupleは中身がCopy traitを含むやつだけの時、(i32, String)とかはダメ。
Ownership and Functions
関数に入れた時は、スコープを外れる時だよ。Copy traitがないと厳しい。
fn main() { let s = String::from("hello"); // sがスコープに入る takes_ownership(s); // sの値が関数にムーブされ... // ... ここではもう有効ではない let x = 5; // xがスコープに入る makes_copy(x); // xも関数にムーブされるが、 // i32はCopyなので、この後にxを使っても // 大丈夫 }
Return Values and Scope
関数に入れる時も、戻る時もmoveが起こるが、「所有権を元の変数に戻したい時」は厄介です。以下のような関数を書かないといけないですが、そんなめんどくさいことしなくても referenceという機能を使えばいけるらしい。
fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); println!("The length of '{s2}' is {len}."); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len() returns the length of a String (s, length) }
4.2. References and Borrowing
↑で話した「所有権を渡したくない時」は参照を使いましょう。
こうすれば、所有権を渡さなくていいらしい。
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // '{}'の長さは、{}です println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
参照を用いて所有権を一時拝借することを borrowingという。借りたものを勝手に変えちゃいけない制約がある。参照は不変です。
以下はエラーが出ます。
fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { some_string.push_str(", world"); }
Mutable References
変更可能な借用ができる。これ。
fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
そして、変更可能な借用は一度に複数貸すことができない。逆に変更不可だったらいける。
let mut s1 = String::from("hello"); let r1 = &mut s1; let r2 = &mut s1; // error let s2 = String::from("hello"); let r1 = &s2; let r2 = &s2; // ok
この制約を設けることで、データ競合が起こらない。
Dangling References
ダングリングポインタというものがあってだな、他人に渡された可能性のあるメモリを解放してしまった時発生する宙に浮いたポインタのことです。
それを発生させないようにRustでは、ポインタがスコープを抜けるまでデータがスコープを抜けることがないように確認してくれます。
これをコンパイルエラーしてくれる
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s }
4.3. The Slice Type
とある変数の中に入っている文字の、文字数を別の変数に保存する。
そして元となった変数を消した時、文字数を保存している変数が消えないことが嫌らしい。...そうか?もちろん同期してくれたらめっちゃ楽だけどそんなことできるんか?
できるんです、Rustなら。
まずはsliceのやり方。
let s = String::from("hello"); let slice = &s[0..2]; // "he" let slice = &s[..2]; // "he" let slice = &s[2..]; // "llo" let slice = &s[..]; // "hello"
ちなみに、&strというスライスを表す型は&Stringの意味を包含します。
文字列だけじゃなくて、配列に対するスライスもあるよ。
let a = [1,2,3,4,5]; let slice = &a[1..3]; // &[i32]型
視覚的な概念図はこんな感じ。

所有権がsにずっとあるから、sが消えることに対してのアラートを出すことができるんだ。所有権と所有権を持っていない関係が、片方だけ消えるってことを抑制しているんだね。
Rustを触ってみた②【備忘録】
こちらの続きです。
- 3. Common Programming Concepts
3. Common Programming Concepts
この章は他のプログラミング言語にもあるような概念が、Rustではどのように表現されているかを示すらしい。慣例も話してくれるし、変数、型、関数、コメント、制御フローなどを学びます。
3.1. Variables and Mutability
Variables (変数)
デフォルトでは変数は不変です。理由はこのルールの方がバグの発生をより防ぐことができるからです。
もちろんmutといれたら可変になるよ。変数の宣言はletと言ってね。
fn main() { let mut x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }
Constants (定数)
定数?変数のimmutableと何が違うん?
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
スコープの縛りはないから、ハードコードしないといけない値に名前をつけてあげて。
Shadowing
同じ名前で変数を再宣言するやつ。再代入には厳しいけど、再宣言はいいよって感じなのかな?
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); // 12 } println!("The value of x is: {x}"); // 6 }
また、再宣言する時に前の型を踏襲しなくてもいい。これはmutでやろうとするとエラーが出るやつ。
let spaces = " "; let spaces = spaces.len();
3.2. Data Types
Rustは静的型付け言語なので、コンパイル時に全ての変数の型がわかっていなければなりません。値と使い方から推測することができます。
Scalar Types (スカラー)
4つあります。
Integer Types
小数部分を含まない数値。
- iが符号を含み、uが符号なしです。
- bit数によって決まります。
- 例) i8, u16, i32, u64, i128, usize
- sizeはプログラムが実行されているコンピューターの型に依存します。
値自体は
アンスコを入れてわかりやすくできるし(12_000_000)
値の末尾に型を指定できるし(53u16)
逆に先頭で指定もできる(0xff)
オーバーフローした時は、developモードではpanicになり、releaseモードの時は0に回り込みます。オーバーフローしたかどうかのメソッドがいくつか提供されているので、必要であればそれを使ってください。
演算子は普通に使えます。
- Floating-Point Types
f32とf64の2種類。デフォルトでf64です。全部符号ありです。
fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 }
- Boolean Type
boolです。
fn main() { let t = true; let f: bool = false; // with explicit type annotation }
- Character Type
fn main() { let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻'; }
Compound Types (複合型)
複数の値を一つの型にまとめられる。タプルと配列という2つのプリミティブな複合型がRustにはある。
- Tuple Type
タプル型
- 固定された長さ、宣言後にサイズが変化しない
- 各位置の型は異なっていいです
- インデックスが決まり、それぞれドットで繋げてアクセスできる
fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of tup.0 is: {0}", tup.0); // 500 println!("The value of y is: {y}") // 6.4 }
- Array Type
配列型
- 固定長
- 各要素の型が同じでないとだめ
- 型の定義の仕方ちょっと特殊
fn main() { let a: [i32; 5] = [1, 2, 3, 4, 5]; }
ベクター型という標準ライブラリで提供されている型があるのだが、そっちはサイズを変化させられる。配列かベクターか迷ったら、ベクター使った方がいいらしい。もし固定長なら配列。
初期値の定義がちょっとおもろくできるよ。あとアクセスはindexを指定してね。
fn main() { let a = [3; 5]; // 3の初期値で長さが5 let second = a[1]; }
もし存在しないindexの値を指定したら、すぐにpanicを起こして不正なインデックスにアクセスさせることを防ぎます。
3.3. Functions
Parameters
関数です。スネークケースで書いてください。ちゃんとスコープを考えて定義されていたら、関数はどこに定義していたとしても構いません。
パラメータも想像に難くない書き方をします。
fn main() { print_labeled_measurement(3, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {value}{unit_label}") }
Statements and Expressions
ステートメントと式の違いはわかった方がいい。
- Statements(ステートメント): 何らかのアクションがあるけど、値は返さない命令
- Expressions(式): 何らかのアクションを行い、値も返す「ことができる」命令
ぶっちゃけ式の説明はあっているかわからない、けどそうっぽいんだもん。返さないこともできるからね。
let y = 6; みたいな書き方はこれはステートメント、戻り値をかえしません。そしてステートメントは末尾にセミコロンをつけます。
逆に以下のような書き方があった場合、
fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {y}"); }
このyに代入しているこれ。
{
let x = 3;
x + 1
}
これは最後の x + 1は式です。これが値を返して、yに代入します。そして式にはセミコロンをつけません。つけたらステートメントになってしまい、値を返さないようになってしまいます。
ちなみに、式→ステートメントの順番で書いたら怒られた。その順番で書くのは基本的にダメっぽいな。セミコロンが最後はいらないのは、そういうことか。ずっとセミコロンを「改行」っていう意味でしか考えてなかったから、「最後は改行いらないからセミコロンなくていいんだろうな〜〜」くらいにしか思ってなかった。
Functions with Return Values
戻り値設定しようね。基本的には最後の式から返される。
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {x}") }
↑の例だと、five() 関数の5にセミコロンをつけたらコンパイルエラーになります、戻り値をi32と言っているのに、何も返さないようになっちゃうので。
3.4. Comments
こうやってね、終わり。なんか複数行に渡るコメントの仕方がないんだな。
// So we’re doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will // explain what’s going on. fn main() { let lucky_number = 7; // I’m feeling lucky today }
3.5. Control Flow
ifとループに関するお話。
if Expressions
普通の書き方をします。ifの直後の条件のようなものは arm と呼ばれたりもする。
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
なお、if文の条件は必ずboolじゃないとダメです。「 1はtrueでしょww」とかいう甘えは許されません。
Handling Multiple Conditions with else if
else ifもあります。けど煩雑になりがちで、matchというものを使った方がいいらしい。それは6章で。
fn main() { let number = 3; if number < 5 { println!("low number: {number}"); } else if number < 15 { println!("mideum number: {number}"); } else { println!("condition was false"); } }
Using if in a let Statement
ifは式だから、letの右にかけるらしいぜ。
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }
これ、5と6の右にセミコロンつけて、何も返さないようにしたらどうなるんだろ、って思ってやったら一応行けた。だけどwarningはでた。
これ、ifの条件によって型を変えることできるんかな?って思ったらできなかった。i32とi64とかでもダメだった。
つまり、実行時に型が決まるような変数はRustにおいてはできないってこと。
TSとかだったら、リテラル型とかできるけど、それもできなそうだしなぁ。強いていうならタプルって感じか。
Repeating Code with loop
loopは永遠に繰り返す。breakとかで抜けようね。
fn main() { loop { println!("again!"); } }
うわ、if も loopもセミコロンつけてもつけなくてもコンパイルエラー起こらん。。式だからか。。
Returning Values from Loops
loopは式だから、letの横に置けるらしい。
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }
そして、なんじゃ、そのbreak...。見たことないぞ、、だけど直感的にわかりやすくて最高だぞ。
そして、breakの代わりにreturnを使ってみようと思ったけど怒られた。return って関数の戻り値だから、いまのloop で使うとmainを抜けちゃうわ。
Loop Labels to Disambiguate Between Multiple Loops
loopは重ねられる。そして、breakとcontinueはすぐ外のloopに対して適用されます。けど、labelというものをつけるだけで、どのloopから抜けるか決めることができるっちょ。
fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }
えぇ。。。書き方キモォ。。 シングルクオートを閉じないだと??すげぇな。めっちゃTypoしそう。
Conditional Loops with while
whileはこう書くよ。
fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }
Looping Through a Collection with for
forはこう書くよ。
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }
配列に対してはこれを使った方がいいね。
Rustを触ってみた①【備忘録】
はじめに
いままでRustを触ったことがなかったので、触ってみる。
環境構築から含めてやってみよう。
公式の紹介を読んでみる
これを読んでいきながら、Rustの良さを最初に知ろうかな。
Rustのいいところ①:パフォーマンス
ガベージコレクタがないらしくて、これがパフォーマンス重視に繋がるらしい?なんでだろ、だって自動で不要なメモリを削除してくれるのがガベージコレクタでしょ?それがどうやってパフォーマンスに繋がるんだろうか。
- GC(ガベージコレクタ)は実行時のオーバーヘッドが存在する
- Rustはスコープを出た瞬間に変数に入った値もオブジェクトも破棄される
- 値の所有者になれる変数は一時期に一つだけ
- 変数から変数に代入したり、関数に代入したりして引き渡すとその所有者も引き渡され、元の変数は参照できなくなる
所有権という考え方がおもろいっすね。
Rustのいいところ②:信頼性
所有権モデルによってメモリとスレッドの安全性が保証されるって。
あと型いいね、型があるだけで嬉しい。
Rustのいいところ③:生産性
- ドキュメントが豊富
- 有用なエラーメッセージ付きのコンパイラ
- パッケージマネージャ
- ビルドツール
- 公式リンター
- 公式フォーマッター
すげぇーーー
The book
intro
- 低レイヤーの操作(メモリ操作など)もできるし、コンパイラで事前にバグを回避できる。
- パッケージマネージャーとビルドツールを併せ持つ
Cargoちゅうもんがあるらしい。 Rustfmtというフォーマッターもあるって。rust-analyzerちゅうもんが、IDEを強力にサポートするって- 低レイヤーから操作などをするアプリから、Webアプリまでいろんなものに使えるらしい
- Rustはスピードと安定性を求める人のための言語
- スピードは実行速度とコーディングの速度の両方
- コンパイラやコードチェッカーによって安定性も実現される
- (各章でやる内容が全部面白そう)
1. getting started
1.1 install
rustup というツールを使ってinstallするらしい。
dockerでlinuxの環境を立ち上げて、以下のコマンドでRustのinstallを完了させる。
# install rust $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh . . . 1) Proceed with standard installation (default - just press enter) 2) Customize installation 3) Cancel installation >1 . . . stable-aarch64-unknown-linux-gnu installed - rustc 1.79.0 (129f3b996 2024-06-10) Rust is installed now. Great! To get started you may need to restart your current shell. This would reload your PATH environment variable to include Cargo's bin directory ($HOME/.cargo/bin). To configure your current shell, you need to source the corresponding env file under $HOME/.cargo. This is usually done by running one of the following (note the leading DOT): . "$HOME/.cargo/env" # For sh/bash/zsh/ash/dash/pdksh source "$HOME/.cargo/env.fish" # For fish # confirm to surccess installing rust $ rustc --version rustc 1.79.0 (129f3b996 2024-06-10)
rustupを使ってrustのバージョンを変えたい時は
$ rustup update
と打てば大丈夫。
1.2. Hello world!
フォルダ作って、それぞれでプロジェクトをやるみたい、今回の章だと以下
$ mkdir ~/projects $ cd ~/projects $ mkdir hello_world $ cd hello_world
拡張子は .rs で、ファイル名はsnake_caseらしい。
以下のファイルを作る。
fn main() { println!("Hello, world!"); }
そして実行
$ rustc main.rs error: linker `cc` not found | = note: No such file or directory (os error 2) error: aborting due to 1 previous error
あれ、エラーがでたっちゃ。どうやら gcc を入れてないことが問題らしい。
$ apt-get install gcc
もっかい。
$ rustc main.rs $ ./main Hello, world!
おおおーーバイナリファイルが作成されて、出た。
main.rsの解説を見る。
- main関数は特別らしく、rustでは常に最初に実行されるらしい。
- Rustのスタイルは4つのスペースでインデントします。
println!という書き方はrustのマクロを実行するらしい- !をつけるとrustのマクロを呼び出すと知っておいてほしい。
- あと式はセミコロンで終わります。
- 生成されるバイナリファイルはrustがinstallされてなくても動作します。
- これはコンパイルするコマンドがいるというデメリットに対する恩恵です。
あとこの段階でVSCodeの rust-analyzerという拡張機能は入れたよ。
1.3. Hello, Cargo!
Cargoとはビルドシステムであり、パッケージマネージャです。
実はinstallはrustupでinstallされていたらしい。
$ cargo --version cargo 1.79.0 (ffa9cf99a 2024-06-03)
cargoを使えば新しいプロジェクトを簡単に始められる。
$ cargo new hello_cargo
Creating binary (application) `hello_cargo` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ ls hello_cargo/
Cargo.toml src
今回はgitレポジトリを作っていないが、それはgitプロジェクトの中にあるかららしい。もしなかったら作成される。
Cargo.tomlというファイルができているが、これはcargoの設定ファイルです。
[package] name = "hello_cargo" version = "0.1.0" edition = "2021" [dependencies]
- [package] の次に書かれている最初の3行はプロジェクトをコンパイルするために必要な情報です。
- [dependencies]はプロジェクトの依存関係を列挙するらしい。
- Rustではパッケージのことを crates (クレート) と呼ぶらしい
rustcではなくcargoでbuildしてみましょう。
$ cargo build Compiling hello_cargo v0.1.0 (/usr/src/projects/hello_cargo) Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.36s
するとrustcとは打って変わって、22個もの新規ファイルができます。
Cargo.logk: プロジェクトの依存関係のバージョンを固定する- defaultではdebugモードでのbuildを行う
- buildして実行をする時は、
cargo runというコマンドをする - 前回のbuildと変更がない場合はbuildしない
- コンパイルする必要があるかどうかをcheckするコマンドが
cargo checkです cargo checkはbuildに比べてはるかに高速なので、それだけを確認したい場合はこちらを使いましょう。
- コンパイルする必要があるかどうかをcheckするコマンドが
- releaseするためのbuildは
cargo build --releaseです。これは実行速度は速いですが、buildに時間がかかります。
2. Programming a Guessing game
指定された数字を当てるゲームを実装します。実装を通じて基本的な概念を学びましょう。
$ cargo new guessing_game
$ cd guessing_game
んで、ここら辺になってきたくらいで target内のファイルがウザくなってきたので、.gitignoreファイルを用意した。
# Generated by Cargo # will have compiled files and executables debug/ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk
あとrustfmtとかがいつまで経っても効かないことに気づいたので、devcontainerで起動した。あれ、devcontainer ってこんなに直感的に簡単に起動するっけ...?
{ "dockerComposeFile": ["../compose.yaml"], "service": "rust", "workspaceFolder": "/usr/src", "customizations": { "vscode": { "extensions": [ "rust-lang.rust-analyzer" ] } } }
それでも適用されないと思ってたら、どうやらtomlの位置をvscodeに指定してあげないといけないらしい。↓をしたらできた。
RustのCargo.tomlの場所がrootに無い時にVSCodeでrust-analyzerのエラーを回避する方法
そしてコードを書いた。
use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }
- Rustは標準ライブラリに定義されたアイテムのセットを持つ
- このセットのことを prelude (プレリュード)と呼ぶ。
- 使いたい型がpreludeにない場合はuse文で明示的に宣言する。
- しかし今回の場合は、useで呼ばなくても、
std::io::stdin()と呼び出すこともできる。
- しかし今回の場合は、useで呼ばなくても、
- Rustは基本的に変数はimmutable(不変的)です。もしmutableにしたい場合は
mutと宣言しましょう。 Stringは文字列型を表し、newで新しいインスタンスを返します。- 間の
::構文はnewがString型の関連関数であることを示します。- 関連関数とはある型に対して実装される関数のこと
- 関数の引数にポインタを代入しているが、基本的にポインタもimmutableである。
- 不変な変数を参照させるためには、
&guessではなく&mut guessと書く必要がある。
- 不変な変数を参照させるためには、
- read_line()はR
Resultというenumを返す。- enumのvariantは
OkかErr Okの場合expectはOkが保持している戻り値を取り出し、それを返す。Errの場合expectはクラッシュさせ、引数で受け取った値を返す。
- enumのvariantは
これで実行させるとこんな感じ
$ cargo run Compiling guessing_game v0.1.0 (/usr/src/projects/guessing_game) Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.11s Running `target/debug/guessing_game` Guess the number! Please input your guess. 3 You guessed: 3
ランダムな値を取得する関数が必要だが、Rustの標準ライブラリには存在しない。ここでcrateを引っ張ってくる必要が出てきた。
[dependencies] rand = "0.8.5"
これでbuildをすると、randはもちろん、randが依存しているcrateまでもコンパイルする。crateのバージョンをプロジェクト内で一致させたい場合は、Cargo.lockを共有するようにしましょう。新しいバージョンのcrateを使いたい場合はcargo updateで更新されます。
それではrandを使って、追記しましょう。
use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); . .
rand::Rngと呼び出しているのになんでRng::とメソッドを実行しないんだ??と思いましたが、どうやら「クレート」という概念らしい、後ほど学ぶって。
あと1..=100という範囲の書き方がキモい、みたことなさすぎる、=ってなんやねん。
この後に比較のためのコードを書いたけど、armっていうものとPatternっていうものがあってそれが強力らしい。どうやらarmとは条件分岐した先の文のことらしいな
同じ変数名を再宣言できるらしい、型変換の時によく使うって。
Stringにあるtrim()メソッドは前後の空白や改行を消してくれるやつで、intへの型変換の際によくやる。
rustの静的型付けは、シナリオ的な型推察をすることもできるよ。
parseはエラー起こることがとてもよくしばしば起こるので、Resultを返すよ、expectで条件分岐しとこうね。しかもどうやらparseは左で定義した型を見てどういう型でparseすればいいか推察する。キモいのとすごいのと。
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
これで、1回だけ推察できるアプリはできた。じゃあ次は推察を何回でもできるアプリをやってみよう。その時はloop()を入れて、breakを入れてあげればいいよ。
そして、別の記述で、こんな書き方ができる。これはstringを受け取っちゃった時に、loopをやり直すような処理。lambda関数みたいなインスタンスでこんなの定義できるんだ、いいね。
// let guess: u32 = guess.trim().parse().expect("Please type a number!"); // ↓ let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
最終的にこんな感じのコードに!!お疲れ様でした。
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
GoFのデザインパターンの記事を読んでみて【備忘録】
- 1. Iterator パターン
- 2. Adapter パターン
- 3. TemplateMethod パターン
- 4. FactoryMethod パターン
- 5. Singleton パターン
- 6. Prototype パターン
- 7. Builder パターン
- 8. AbstractFactory パターン
- 9. Bridge パターン
- 10. Strategy パターン
- 11. Composite パターン
- 12. Decorator パターン
- 13. Visitorパターン
- 14. Chain of Responsibility パターン
- 15. Facadeパターン
- 16. Mediator パターン
- 17. Observer パターン
- 18. Memento パターン
- 19. State パターン
- 20. Flyweight パターン
- 21. Proxy パターン
- 22. Commandパターン
- 23. Interpreterパターン
1. Iterator パターン
listのオブジェクトを用意するときに使うパターン。
「ID順」「作成順」などいろいろな走査パターンがあるが、それをListクラス自体に用意するのは柔軟性がない。
なので、Listクラスとは別に走査を管理するクラスを作ることによって柔軟性を持たせるパターン。
- Listクラス自体は変化しうるもの
- Listクラスの走査パターンも変化しうるもの
という前提であれば、Iteratorパターンはとても有効。Listクラスによって、使う側がコードを変えなくて済むようになる。
たぶん本質はAggregateIFとIteratorIF。この二つがListという概念をいい感じに抽象化している。
2. Adapter パターン
インターフェースに互換性がないクラス同士を組み合わせるときに使うパターン。
...そんなときあるんか?とも思うが、「これまで使っていたメソッドの上位互換のようなメソッドを持つクラスがあった場合、インターフェースの違いを吸収するためにAdapterを準備する」のような使い方をするっぽい。
やり方は継承を使うパターンと委譲を利用するパターン。それぞれ方法は違えど、やっていることは これまで使っていたメソッド を利用するだけ。
3. TemplateMethod パターン
templateとは文字の形に穴が空いている薄いプラスチックの板を意味しており、マジックで書くか鉛筆で書くかは自由だけど製作物の形は決めちゃうぜっていうもの。
- 固定で決めるもの=メソッドを使った処理のフロー
- 自由に後から決めるもの=メソッドの具体的な実装
です。 例えば「カレーを作る」というものがあったら、
public abstract class CookCurry{ public abstract void cut( Ingredient[] yasais ); public abstract void niru( Ingredient[] yasais ); public abstract void putSpice( Ingredient[] yasais, Spice spice ); public void createWoodCutPrint(){ Potato potato = new Potato(); // Potato クラスは、Ingredient インタフェースを実装している Carrot carrot = new Carrot(); // Carrot クラスは、Ingredient インタフェースを実装している Ingredient[] ingredients = {potato, carrot}; Spice spice = new Spice(); cut( ingredients ); niru( ingredients ); putSpice( ingredients, spice); } }
このスーパークラスを満たすようにサブクラスでメソッドの具体的な実装を定義したら完了。
4. FactoryMethod パターン
3の場合ではメソッド自体しか自由度がなかったのですが、処理内のオブジェクト生成にも自由度を与えたい時に使うのがFactoryMethodパターンです。
3の例を使うと、
public abstract class CookCurry{ public abstract void cut( Ingredient[] yasais ); public abstract void niru( Ingredient[] yasais ); public abstract void putSpice( Ingredient[] yasais, Spice spice ); public Ingredient[] createIngredients() { Potato potato = new Potato(); Carrot carrot = new Carrot(); Ingredient[] ingredients = {potato, carrot}; return ingredients; } public Spice createSpice() { return new Spice(); } public void createWoodCutPrint(){ Ingredient[] ingredients = createIngredients() Spice spice = createSpice(); cut( ingredients ); niru( ingredients ); putSpice( ingredients, spice); } }
createSpice、createIngredientsをサブクラスでオーバーライドできる。
5. Singleton パターン
クラスのインスタンスが一つしかないことを保証するためのパターンです。
コンストラクタを private とすることで、他クラスから新たにインスタンスが生成されないような構造とすることで、インスタンスの生成を制御します。
Javaではインスタンスを作成する時に、 new Hoge()とするがそれをできないようにさせる。
代わりにHoge.getInstaunce()のように取得させるようにし、返すインスタンスは何度やったとしても一つの既存の作成済みインスタンスしか返さないようにするというものだ。
6. Prototype パターン
同じ作業はコードや関数として落とし込むんじゃなくて、オブジェクトのメソッドやコンストラクタとして落とし込んだ方がめっちゃ楽だよねって話。
そりゃそう。
7. Builder パターン
家の建築でいうところの、「家の建築過程」と「素材」の役割を明確に分離するイメージです。
作成過程を Director、表現形式(上でいう素材)を Builder に任せて、それぞれを自由に組み替えられるようにすることで柔軟性を生みます。
これTemplateMethodパターンと似ているんですが、違いとしては以下の2つ
- Builderパターンの目的はオブジェクトを生成することであり、アルゴリズムを実行することではないこと。
- Builderパターンではインターフェースとして分離しているが、TemplateMethodは継承を使って分離している
あと名前もいいね、わかりやすい。DirectorとBuilderって。
最終的にDirectorの引数に具体的な実装のBuilderを注入して実行する形になります。
8. AbstractFactory パターン
これってBuilderパターンと達成したい目的が全く一緒な気がするんだが??
と思っていろいろ調べてたんですが、個人的にはこのサイトがすごいしっくりきました。
イメージは以下のような感じ。ハンバーガー屋さんに例えました。
- Builderパターン → バンズ、パティ、ソース、野菜は全部お客さんが自由に選べます!選んでもらったらこっちで作ります。
- AbstractFactoryパターン → 照り焼きバーガー、チーズバーガー、お月見バーガーなどのメニューを選んでください!作り方と中身はメニューに合ったものをこちらでやります。
みたいな感じかな。どっちを選ぶかは方針次第、複雑度や制約性などを考えながらだと思います。クライアントに見えている部分の抽象度が違うね。
実装自体は製作過程と表現方法を両方とも一括のインターフェースを定義して、その実装をパターン分だけ用意する感じ。
9. Bridge パターン
例えば色が2種類選べるバッグがあるとする。その時クラスは2つなのだが、もしオプションでポケットをつけれるとしたら、クラスは2x2の4種類になってしまう。そこから機能が追加されればされるほどクラスが膨大になるの?? という問題に対してアプローチした解決策。
ぶっちゃけFactoryMethodと同じような感じがするが、こっちは「拡張性」に重きを置いた感じかと思います。「現状は想定がないけど、もし拡張したいときは簡単にできるように」という想定。
実装にはインターフェースを使いません、継承と埋め込みを使います。
public class Bag { private BagImple bagImple; public Bag(BagImple bagImple){ this.bagImple = bagImple; } public void getColor() { bagImple.getColor(); } } public abstract class BagImple{ public abstract String getColor(); } public class WhiteBagImple extends BagImple { public String getColor() { return "white"; } } public class BlackBagImple extends BagImple { public String getColor() { return "black"; } }
これに対してポケット付きのバッグを定義すると?
public class PocketBag extends Bag { public PocketBag(BagImple bagImple) { super(bagImple); } public String getPocketColor() { return getColor(); } }
これだけで済むね。
10. Strategy パターン
とある実装のアルゴリズムの切り替えを容易にする時に使います。というかさっきのBridgeパターンのWhiteとBlackを切り替えているような実装がまんまです。
ソートのアルゴリズムを容易に変えたい時、ゲームのレベル(アルゴリズム)を容易に変えたい時などに使われるらしい。
まぁやっていることは簡単でインターフェースを定義してそれを満たすようにアルゴリズムをそれぞれ書いて、必要なアルゴリズムを注入する感じ。
11. Composite パターン
自身を含む複数種類のクラスを再帰的に参照をする構造の時に使う。「ファイルシステムを作る時に使うパターン!」と覚えておいたら結構楽かも。
再帰的に参照をするクラスのインターフェースを定義しておいて、それに対して実装を行うことで楽になる。
12. Decorator パターン
一つのコアに対して装飾をどんどん追加していきたいときに使います。「クレープにトッピングをどんどんしてくパターン!」と覚えておいたら楽かも。
解決策がCompositeパターンのように再帰を行うので、どっちを使えばいいのかわからなくなりそう。Compositeパターンとの違いはたぶん以下。
- Decoratorパターン → 一つの被オプションに対してオプションだけをどんどんつけていくイメージ
- Compositeパターン → 被オプションに対してオプションをつけることもできるし新たな被オプションをつけることもできるイメージ
13. Visitorパターン
なんかこれめちゃめちゃむずくないか。本質を捉えるのが大変だった。たぶん嬉しいことが、Visitorのvisitっていうメソッドと、Accepterのacceptっていうメソッドを作るだけで、お互いやりたいことが自由にできるってことだとは思う。
けどこれってIFを通じて双方向に依存しているだけな感じがしてならない、果たして美しいのか。
参考↓
14. Chain of Responsibility パターン
責任の鎖をつなぐようなパターン。社員→課長→部長→社長と判断できるところまで稟議が上がっていくような設定を組むことができる。
実装自体は簡単で、nextを設定できるようなsetNext()メソッドをIFを共通で設定しておいてあげるだけ。
15. Facadeパターン
Encodeがめちゃめちゃ複雑な実装だとしても、それを利用する側からしたらencodeというメソッドを実行するだけで済むようにしてあげたほうがいいよね、っていう当たり前のお話。
16. Mediator パターン
飛行機と管制塔の関係に似ています。飛行機は飛行機同士で着陸のタイミングを連絡し合うのは至難の技です。しかし、飛行機は管制塔と連絡しさえすれば管制塔が支持を出してくれます。
↑のように複雑な関係性を一つの調停者が管理することで、依存関係を制御することができます。
具体的な実装は、各オブジェクトは調停者のIFに依存し、調停者の実装はオブジェクト共通のIFに依存し、IFを通じて会話をします。
17. Observer パターン
親が子のことをずっと監視するのではなく、子が親に報告するようにしたら、親の管理が楽だよねって話。
イベントあったら発行してね的な感じ。
18. Memento パターン
「あの時これやったなぁ」を記憶しておいて、過去にやったことをすぐに利用できるようにして、同じことを2度やらないようにしたパターン。
例えば、足し算のようなもの。 「1 + 2 + 3」をしたら、それをMementというオブジェクトにアウトプットして、辞書型配列に名前をつけて保存しておく。 「1 + 2 + 3」を使った計算をしたかったら、辞書型配列から呼び出して、Mementを復元して計算に加える感じ。
19. State パターン
状態ごとにメソッドや値が変化する時、その状態を条件としてメソッドや値の条件分岐を実装するのが一般的だが、状態のパターンを増やした時条件分岐も増やさないと行けないのが厄介である。
よって各状態ごとのクラスを定義しておけば、そのクラスの条件分岐をするだけで済むよねってお話。
20. Flyweight パターン
キャッシュのようなもの。重たくて何度実行してもレスポンスが変わらないメソッドの場合、一度実行した結果はどこかに持っておいて、2度目以降呼び出された時はそれを返すようにしようってやつ。
21. Proxy パターン
Proxy は代理人という意味です。クライアントからのリクエストを代理人が受けとり、代理人が答えられるものは答えて、答えられないものは本人に聞いて、その回答を利用して答えるようなものです。
Chain of Responsibilityパターンとすごい似ている感じがしますが、違いは以下
| Proxy | Chain of Responsibility | |
|---|---|---|
| 連結するクラス | 原則2つ 代理人と本人 |
いくらでも |
| 上位のクラスの呼び方 | 直接呼ぶ | passすれば実行が自動的に上位に移る |
| メソッドの実行 | 上位クラスの結果を利用できる | 上位クラスの結果を利用できない |
ごめん、ぜんぜん似てなかったわ。
22. Commandパターン
「あるオブジェクトのメソッドが今後増える可能性がある時、そのメソッドをオブジェクト内で定義するのではなく、関数として独立して定義しその関数の引数にオブジェクトを渡してあげるようにすれば良くね?」というのがCommandパターン。
「オブジェクトのメソッド」というよりも「オブジェクトを操作する」というようなニュアンスが増えた感じだね。
23. Interpreterパターン
構文木で使われたりするパターン。だけどCompositeパターンと構造はほぼ全く一緒。Compositeは add() メソッドがあるが、Interpreterには特にない、くらいかな。
Compositeパターンはフォルダ構造とかを表現する時に使って、
Interpreterパターンは構文木構造とかを表現する時に使うよ。
継続的デリバリーのソフトウェア工学 を読んでみて【備忘録】
はじめに
これは「継続的デリバリーのソフトウェア工学」を読みながら記した備忘録です。
第1部
第1章イントロダクション
学びのエキスパート兼複雑さ管理のエキスパートになれ。
学びのエキスパートになるには、5つが大事。
- 反復的な作業
- フィードバック
- 漸進主義
- 実験主義
- 経験主義
複雑さ管理のエキスパートになるには、5つが大事
- モジュラー性
- 凝集度
- 関心の分離
- 抽象化
- 疎結合
ソフトウェア開発の舵取りに必要なツールの5要素
- テスト可能性
- デプロイ可能性
- スピード
- 変数の管理
- 継続的デリバリー
第2章 工学とは何か
ソフトウェアを作る為に考慮することと、 建築物を作る為に考慮することは異なる。
建築物は本質的な設計以外に気にしないといけないこと(土地、予算、材料、輸送)が多いが、ソフトウェアは設計以外を気にするコストが無視できるほど小さい。
工学の定義 「工学とは、実践的な問題に対する効率的、経済的な解を見つけるための経験的、科学的なアプローチの応用のことである。」
第3章 工学的アプローチの基礎
ソフトウェアの生産性を計測する時、「安定性」 と「スループット」の2属性に着目すると良い。
安定性の指標は
- 変更失敗率: 変更によってプロセスの特定の地点でエラーが発生した割合
- エラー修復時間: プロセスの特定の位置で起きたエラーから修復までにかかった時間
スループットの指標は
- リードタイム: 「アイデア」を「動作するソフトウェア」に変える1行の変更にどれだけの時間がかかっているか。開発プロセスの効率性の指標。
- デプロイ頻度: 変更はどれくらいの頻度で本番環境にデプロイされているか。変更ベースの指標。
第2部
第4章 反復的な作業
学びは基本、イテレーション(繰り返しで、目標に少しづつ近づく)でおこなう。
ウォーターフォールは、 - 時間とともに変更のコストが大きくなる - 一番最初に全ての計画が建てるはずという考え
アジャイルは - 変更コストが時間を追っても一定にするために努力する→リリースを何度も行う - 一番最初に全ての計画は建つはずがないという考え
第5章 フィードバック
「評価を評価元になる主体に伝達すること」がフィードバックであり、それがあれば意思決定のエビデンスを明確化できます
フィードバックを効率的に回すためには、 フィーチャーブランチ(FB)によって作業分担し、マージの担保を継続的インテグレーション(CI)によって担保する。
設計に対するフィードバックはTDDを非常に高く評価します。TDDを使うことで、以下の5つを強制的に担保できるからです。
アーキテクチャに対するフィードバックはテスト可能性とデプロイ可能性を追求し続けたら自ずとよいアーキテクチャになってる不思議。
失敗は早くしろ。
組織として、目標に向かっているかどうかを判断するための軸として適応度関数を定義するのは大事だが、「安定性」と「スループット」は結構ベストよ。
第6章 漸進主義
漸進的な設計はモジュラーな設計の応用なので、あとで様々なコンポーネントが交換可能
ロケット全部を一気に作るんじゃなくて、「出発するためのエンジン」「帰るためのエンジン」「宇宙で過ごすためのドック」とかで分離することで、作成・検証の工数を下げた。
↑これは分離の重要性が直感的にめっちゃわかる。
漸進主義(モジュラー設計の応用)をすると、チームは独立して開発を進めることができる。そのためには「開発するシステムを独立性高く分解する」か「継続的インテグレーションによって変更のFBのスピードと品質をあげる」かです。
ポートアンドアダプターがとても良いぞ。インターフェースと実装を分離しよう。
第7章 経験主義
事実を積み上げて語ろう、これはバグ修正も実装自体にも言える。
第8章 実験主義的であること
客観的な指標で物事を語ろう。
大事なのは
- フィードバック
- フィードバックのスピードをあげるだけで、改善の効率が上がり、ものは良くなる
- 仮説
- 仮説をたて、実験し、差分を計測し、改善する。仮説は実験の第1歩。
- 計測
- 計測の値に人のバイアスはないか、計測している値は取りたい値なのか、など考慮が必要
- 変数の管理
- 考えなくていいことは、自動化して考えなくてもいいようにしよう。じゃないと考慮しないといけないことが多くなる。
そういった意味でTDDはガチでおすすめ。実験主義的に開発を進められる。
第3部 複雑さ管理の最適化
第9章 モジュラー性
モジュラー性はどれだけ独立に分離がされているかです
モジュラーの切り分け方はソフトウェア開発のスキルそのもの、この優劣が玄人を分ける。
TDDをするためにはモジュラー性は必須
モジュラーの繋ぎ目(シーム)は何が良いかちゃんと考えて。
第10章 凝集度
凝集度は、依存度が高いコードをどれだけ同じ場所にまとめられるかです。とある場所のコードを変えた時に別の場所でコードを変えるようなことにならないように。
しかし、ただ同じファイルや同じ関数にまとめたからと言って凝集度が高いわけではない。構造的に(フォルダシステムみたいに階層的に)まとめる必要がある。詳細度が異なるものを並列にまとめたくないねってお話。
第11章 関心の分離
抽象度が同一のものをまとめても、「凝集度が上がる」というらしい?
関心の分離 = 抽象度が同一のものをまとめたときに、サービス全体の方針といま書いているモジュールの責務を考え、何を入れるか入れないかを考える
ポートアンドアダプターは、サービスレベルだと「ヘキサゴナルアーキテクチャ」とも言う
個人的な理解として、 「関心の分離」は言葉通りの意識を持つことであり、それによって様々な恩恵がある。
縦の関係(抽象度)と横の関係(コンテキスト)それぞれ分離すること大事
第12章 情報隠蔽と抽象化
抽象化や情報隠蔽を進めることで、モジュール間のカップリングを防ぎ、コードの読解容易性を上げ、その結果として変更容易性をあげることに直結する
YAGNIのメリットは変更容易性をあげることです。「将来的に必要になるであろう機能」を作った人がその機能を消すことを嫌がる現象を減らすことができます。
「抽象化の漏れ」は細部がまだ完全に抽象化されていない状況のことであり、大体2種類の原因がある。
一つは細部の情報が必須なために絶対に抽象化の漏れが避けられない場合。
もう一つが設計の破綻に対処するための時間・エネルギー・想像力が足りないために残ってしまう場合。
抽象化は解決したい問題と状況によって最適が異なる。
イベントストーミングとは↓
第13章 カップリングの管理
カップリングはモジュール同士の依存の度合い、密接に繋がっているのかの尺度
独立してデプロイできることが、マイクロサービスで1番難しいけど意味があること そしてそれはサービスのスケーラビリティにつながる
疎結合と関心の分離は異なる概念
第4部 ソフトウェア工学を支えるツール
第14章 工学分野のためのツール
いままで学んだことを活かしてテストを書こうね。
デプロイの速さとテストは大事だよ。
第15章 現代のソフトウェアエンジニア
プロダクトを作る上で2つの流れがあるが、OBAP(Organization→Business→Architecture→Process)の順番に決めずに、BAPO(Business→Architecture→Process→Organization)の順番に決めたほうが本質に向かってます。