プログラマ(中の下)級者になろう
こんにちは!まむばるです!
今日はプログラマが初心者から中級者になるときに持っておくとよい感覚を言語化していきます!
私も長くプログラムを書いていますが、すべてが完璧なものはないです。
ただ、ベターなプログラムの書き方 がありますので、参考にしていただけたらと思います。
(
プログラムは流派・宗教性があると考えていて、信念・信条によって、合う合わないがあります。それぞれいがみ合わずに尊重しあえる関係がいいなぁ~。まむお
私はVimmerです!emacs使える人マジ尊敬~!
)
プログラムは書けば書くほど上達していきますが、
方向性や目指すべきゴール、そして観点を持ち合わせておくことにより、効率的に上達ができると思います。
ぜひ、一朝一夕では獲得しえないスキルを身に付けていきましょう!
では、具体的に見ていきましょう。
前提
私は、最近typescriptでプログラムすることが多いので、typescriptをプログラムイメージとして採用しています。
ベースはJSですので、ある程度プログラムを書いたことがある人だったら伝わるようにしたいなぁ~
言語依存系プログラマーにはならないでくださいね!
弘法筆を選ばず、真に優れたプログラマーならfortranでもCOBOLでも古典言語でも優れたプログラムをかけるはず。。。
(あり・をり・はべり・いまそかり~👘、いふ・ふぉ~・りた~ん・ぶれいきん~🕺🏿)
可読性の高いプログラムの作り方
趣味プログラマや初心者では抜けて落ちてしまいそうな観点です。
プロでお金をもらいながら働いているプログラマには必須の要件です。
ただトライアンドエラーで書いて、動いたらOK!それ以上は触りませんでは、
初心者止まりです。
大事なことは、「1ヶ月、1年後の自分が見ても処理が即座に理解できる」ことです。
そのために具体的な必要事項を記載しておきます。
あくまで私の観点ですので、共感したものを採用いただければと思います。
■ 可読性の観点から実施しておくほうが良い事項
1. ソースコードには必ず doc-string(ドキュメントコード)を残しておく
Javaであれば`Javadoc` , (Type|Java)scriptであれば `JSDoc` などです。
`/**`こんなかんじで始まっているやつです
markdownで記載して、処理が`プログラムベース`ではなく、最低限`自然言語ベース`で伝わる
[こんなやつ]
/**
* 自販機の下に落ちた小銭をぱくる処理
*
* @param {RapidEnum} rapidly ぱくるスピード slowly | normally | rapidly
* @param {LangEnum} lang 話す言語 泉州弁 | 摂津弁 | 河内弁
* @param {function} onSteal パクった金をどう処理するかを定義するコールバック関数
*
* ## 処理概要
* 1. 通行人から金を巻き上げる
* 2. 自販機からパクったかのように取り出す
* 3. コールバック関数を実行する
* 4. パクった金を戻り値として返す
*
* @return {number} パクった金の金額
* @see [【驚がく】相次ぐ“自販機荒らし”の大胆手口「無理矢理バールでえぐり取った形」 同一犯とみられる「銅線窃盗」も… 栃木|FNNプライムオンライン](https://www.fnn.jp/articles/-/758659?display=full)
*/
function stealMoneyUnderTheVendingMachine(
rapidly: RapidEnum,
lang: LangEnum,
onSteal: (money: number) => void,
): number {
// 金出せオラオラ処理
switch(lang) {
case LangEnum.KAWACHI:
speak('おんどれ!なにぶつかっとんねん!あほんだれ!どつきまわすど!われ!金出さんかい!');
:
break;
:
}
}
2. コメントには、設計書やリファレンスなどのURLリンクを必ず残しておく
あとから リファレンスなどを探しにいくのも面倒ですし、他の人が見ても理解を促す資料などにはリンクしておくべきです
Doc系では、よく `@see [たいとる](URL)` のような形式で書くことが多いです。markdownはエンジニアには必須ですね
3. 関数(or メソッド) の規模は大体、エディタの窓枠に収まるサイズで記載する。それを超えたら、関数(or メソッド) を分割する
だいたい 200行を超え始めたら可読性が一気に落ちる 気がします。眠たいときは読めません。。。
if文はtrueの場合と、falseの場合で短い方を上に持ってくる。また、ネストが深くならないように例外系を先に排除する
// これはだめ
if (isOk/*正常系*/) {
// ネスト深なるねん
やたら長くなりがちな正常系();
: // 200行超えたら読めへんわ!
} else {
throw ~~exception; や return;
}
// ------------------------------------
// 雰囲気OKなやつ
if(!isOk/*例外の場合true*/) {
throw ~~exception; や return;
}
正常系(); // ネストが1つ浅くなった
■ 保守性を考えたプログラムの作り方
プログラムは1度書いたら終了ではありません。
メンテナンスをおこなったり、機能追加したり様々な後付けが発生します。
そのため、作成した時点では完璧であってもあとからツギハギされてぐちゃぐちゃなコードに大変身することもあります。
(なんということでしょう~♪ あんまに綺麗だったコードが あら不思議1年でクソコードの完成です)
あとからメンテが入ることを考慮にいれて、プログラムを作成するべきです。
保守性の観点から実施しておいたほうが良い事項
1. 外部連携系のデータはすべてラップして使用すること (特にBackend)
例えば、DB を例にあげると、DBのテーブルに対応するデータクラス(ORMの場合はモデル) をそのままプログラムで使うのではなく、アプリで使う用のデータクラスを別で用意しておく
そうすることによって、DBの絡む変更のみならず、データ加工もしやすくなる大事なのは、 変更があっても改修箇所が1箇所で巻き取れること です
ちなみにデータを変換したり、転送したり、加工したりするので、色々な呼び方がありますが、
オブジェクト指向では転送に着目して、「 `DTO (Data Transfer Object )`」 とも読んだりしますね。
この言葉は使われるドメインによって意味が変わることがあるので注意が必要です!言葉定義にうるさい人にヤカられないように~!笑
```mermaid
graph TD
A(("DBのテーブル")) -->|"変換"| B["データクラス (モデル)"]
B -->|ラップ| C["**アプリ用データクラス**"]
C -->|"使用"| D["プログラム"]
D -->|"変更"| E["改修箇所1箇所"]
```
2. API呼び出し後のレスポンスはすべてラップして使用し、API呼び出しを行うコード(関数)も別で切り出しておくこと (特にFrontend)
DB 同様に、API の レスポンスは結構変更が入ります。そのため、API レスポンスが変更されても 改修箇所が1箇所にまとまっているほうが良いです
3. ライブラリ を使用する場合もラップしておくこと
こちらはめちゃくちゃ辛い思いをされたプログラマの方も多いのではないでしょうか?
ライブラリは様々ありますが、OSS で開発されているものも、個人で開発されているものも色々あります。
ただ、1つ言えるのは、ライブラリは 下位互換を捨てる可能性が高いということです。
具体的には、バージョンを上げると動かなくなる可能性があります。
言語のサポートが切れるタイミングで、バージョンアップしたがる顧客が多いです。
そのタイミングで、言語のバージョンアップを行うことによって、ライブラリの依存バージョンも自動的に上げなければならないことが結構あります。
ただ、ライブラリを直でインポートして使っていた場合、その影響範囲を特定するだけでめちゃめちゃ時間がかかります。
必ずライブラリの用意されている関数は、独自でラップしてから使用しましょう。
そうすれば、独自ラッパーがそのバージョン差分を緩衝材のように巻き取れる可能性が高いです。
// 例えば、日付操作ライブラリの関数をラップする場合
import { format, parseISO } from 'date-fns';
// 日付フォーマット関数をラップ
function formatDate(dateString: string, dateFormat: string): string {
const date = parseISO(dateString);
return format(date, dateFormat);
}
// このようにしておくと、date-fnsライブラリの関数が変更された場合でも、ラッパー関数内で対応するだけで済む
このときに、ライブラリ側のオプショナル引数をそのまま使える形にしておいてあげると、他のプログラマの方も使いやすくて尚良しです。
ソースコードの分割は、先人たちの素晴らしいパターンを参考にする
ソースコードをどのように切り分けるか結構迷いますよね?
以下、私がよく参考にするものを記載しておきます。
詳しく書いてしまうと、だいぶ時間がかかってしまうので、
気になる方は概要から調べて見ましょう!
DDD (ドメイン駆動)
ドメイン駆動設計は、ソフトウェアの設計と開発において、ビジネスドメインの知識を中心に据えるアプローチです。
ドメインモデルを構築し、それを基にシステム全体を設計します。これにより、ビジネスロジックが明確に分離され、保守性が向上します。
オニオンアーキテクチャ
オニオンアーキテクチャは、依存関係の方向を内側から外側に向けることで、ビジネスロジックを中心に据えた設計パターンです。
中心にはドメインモデルがあり、その周りにアプリケーション層、インフラストラクチャ層が配置されます。これにより、ビジネスロジックがインフラストラクチャに依存しない設計が可能になります。
クリーンアーキテクチャ
クリーンアーキテクチャは、ソフトウェアの設計において、依存関係の方向を制御することで、柔軟性と保守性を高めるアプローチです。
アプリケーションのコア部分(エンティティ、ユースケース)は外部のフレームワークやインターフェースに依存せず、逆に外部がコアに依存する形になります。これにより、ビジネスロジックが変更されても、インフラストラクチャやUI層に影響を与えずに済みます。
以下はあくまで構成例です~!
それぞれ作るものや言語によって全然異なってくると思うので、参考程度でお願いちまちゅ♥
/backend
├── /src
│ ├── /domain
│ │ ├── /models # ドメインモデル(ビジネスロジック)
│ │ ├── /repositories # リポジトリインターフェース
│ │ ├── /services # ドメインサービス
│ │ └── /valueObjects # 値オブジェクト
│ ├── /application
│ │ ├── /commands # コマンド(ユースケースの実行)
│ │ ├── /handlers # コマンドハンドラ
│ │ ├── /dtos # データ転送オブジェクト
│ │ └── /services # アプリケーションサービス
│ ├── /infrastructure
│ │ ├── /persistence # 永続化(リポジトリの実装)
│ │ ├── /eventbus # イベントバス、メッセージング
│ │ └── /services # 外部サービス(APIクライアントなど)
│ └── /api
│ ├── /controllers # APIエンドポイント(RESTful API)
│ ├── /middlewares # 認証、認可などのミドルウェア
│ └── /responses # APIのレスポンスフォーマット
├── /tests
│ ├── /unit # ユニットテスト
│ └── /integration # 統合テスト
└── /config # 設定ファイル(環境変数、API設定など)
/frontend
├── /src
│ ├── /components # 再利用可能なコンポーネント
│ ├── /services # API通信、ロジック
│ ├── /views # ビュー(ページ)
│ ├── /store # ストア(状態管理)
│ └── /assets # 静的ファイル(画像、CSSなど)
├── /public # 公開ディレクトリ(index.html など)
└── /tests # フロントエンドのテスト(ユニット、E2Eテストなど)
フォルダ分けがきちんとされていれば、どこにどんなソースがわかりやすくアクセス性も上がります。
また、誰が書いても同じような構成になって、スパゲティのようなぐちゃぐちゃソースにならないので、おすすめです。
ただし、人間が注意するのは限界があるので、ソースレビューをしっかり行う。
また、言語 `linter(コードの静的解析ツール)` や `prettier(自動フォーマッター)` など用いて、コーディング規約レベルのチェックをすることも必要です。
さいごに
プログラムってむずめっすよね!
未だに「あぁ。ここ最初からこう書いておけば綺麗やったのに、今更変えられない。単体テスト終わっちゃったし。。。」みたいなことあるんですよね~。
こればっかりは場数と豊富な知識と選択肢を持っていることが重要ですね。
同じ処理でも10パターンぐらい頭にインプットしておくと、その場その場で最適解を選べるのかも。。。
これからもみなさんエンジニアリングを頑張っていきましょう!
弊社エンジニア募集中です~!ご興味があればぜひぜひご連絡ください!