グラッグ・ブレイン開発者(2022)
(grugbrain.dev)- 長年プログラミングしてきた「小さな脳」の開発者の経験談を通じて、ソフトウェア開発における最大の敵は 複雑性 であり、それを減らす姿勢こそが実務の中心であるべきだと強調する
- 複雑性を防ぐ基本武器は 「no」と言うこと であり、妥協が必要なときは 80/20 解法・プロトタイプ・小さなリファクタリング・遅い抽象化で現実的な解決策を探る
- テストはコードがある程度固まった後に 統合テスト を中心に据え、単体テストと少数のエンドツーエンドテストを補助的に使い、バグは回帰テストでまず再現してから直す
- ツール、型システム、ロギング、デバッガ、単純な API は開発者の記憶と推論の負担を減らしてくれるが、過剰なジェネリクス・コールバック・マイクロサービス・フロントエンドフレームワークは複雑性を増やしうる
- 既存コードと組織プロセスには謙虚に向き合い、理解していないコードをむやみに削除せず、「複雑すぎる」と言える文化が必要である
複雑性は開発者の永遠の敵
- ソフトウェア開発で最も危険な敵は 複雑性 である
- 複雑性はコードベースに徐々に入り込み、1か所を変えると関係なさそうな場所まで壊れるようにする
- グラッグはこれを目に見えない「悪魔」にたとえ、開発者が直接見たり簡単に叩き潰したりできない存在として扱う
- 複雑性は善意の開発者やプロジェクトマネージャーを通じても入り込むことがある
- 機能、抽象化、プロセスが増えるほど、コードは理解しにくくなる
- グラッグ自身も、ときには複雑性を持ち込んだ張本人だったと認めている
「no」と「ok」の使い方
- 複雑性を防ぐ最も強力な武器は「no」である
- 不要な機能を作らない
- 不要な抽象化を作らない
- ただし「no」は優れたエンジニアリングの助言であっても、キャリア上いつも有利とは限らない
- 「yes」はより多くの報酬や管理職につながることがある
- それでも開発者として自分に正直でいるには「no」が重要である
- 妥協が必要なときは「ok」と答えたうえで 80/20 解法を探す
- 80/20 solution は 20% のコードで 80% の要求を満たすアプローチである
- すべての装飾的な機能を備えられなくても、価値の大部分を届けつつ複雑性を抑えられる
- ときにはプロジェクトマネージャーに実装の詳細をすべて説明せず、単純な方法で解決したほうがよいと考える
コード構造化とリファクタリング
- プロジェクト初期にアプリケーションをあまり早く分割すべきではない
- 初期段階ではシステムの形がまだ不明瞭で、何を作っているのかも完全には理解できていない状態である
- 時間が経つと良い 切断点(cut point) が見えてくる
- 良い切断点は、残りのシステムとのインターフェースが狭い
- 少数の関数や抽象化で内部の複雑性を隠せる
- グラッグはこれを、複雑性の悪魔を改修の中に閉じ込めることにたとえる
- 大きな脳の開発者がプロジェクト初期に多くの抽象化を作ろうとするときは、被害を減らす方法が必要である
- UML ダイアグラムのように、コードへ直接被害を与えない成果物へ向けさせることができる
- 「明日動くデモ」を要求すれば、実際に動くコードと現実を早く見られる
- このデモのアプローチを プロトタイプ と呼べる
- リファクタリングはプロジェクト後半、コードが固まってから有用になる
- 大きなリファクタリングほど失敗する可能性が高い
- システムが動き続ける状態を保ちながら、小さな段階で進めるほうを好む
- エンドツーエンドテストはリファクタリング中の命綱になるが、壊れたときに原因を理解しにくいことがある
- 過剰な抽象化はリファクタリング失敗やシステム障害につながりうる
- J2EE は過剰な抽象化の例として挙げられる
- OSGi 導入は複雑性を減らそうとした試みだったが、むしろより強い複雑性を生み、何年にもわたるやり直しが必要だったという
テスト戦略
- テストは多くの時間を救ってくれるが、「とにかく先にテスト」には懐疑的である
- まだドメインを理解していない段階では、何をテストすべきか分かりにくい
- グラッグは、プロトタイプ後にコードが固まり始めるときに大半のテストを書くほうを好む
- 後からテストを書く場合でも 規律 が必要である
- 「自分のマシンでは動く」という理由でテストを省略してはいけない
- 別のマシンや未来の同じマシンで動く保証はない
- 単体テスト、統合テスト、エンドツーエンドテストの役割を区別する
- unit tests はプロジェクト初期には役立つが、実装変更で壊れやすく、リファクタリングを難しくすることがある
- end to end テストはシステム全体の動作を示すが、壊れたときに原因を特定しにくく、頻繁に壊れると無視されがちである
- integration tests はシステムの正確性を確認するには十分高く、デバッガで原因を見るには十分低いため、「スイートスポット」に近い
- 理想的なテスト構成は次のとおりである
- 初期には一部の単体テスト
- 切断点ができ、システムが安定するほど強力な統合テスト
- 最もよく使われる UI 機能と重要なエッジケースだけを含む、小さくよく管理されたエンドツーエンドテスト
- モックはまれにしか使わず、必要な場合でもシステム境界のような大きな単位でのみ使うほうを好む
- バグを直すときは例外的に、まず回帰テスト でバグを再現してから修正する
プロセス、アジャイル、既存コードへの姿勢
- アジャイルは最悪ではないが、良いとだけも言いにくい
- 開発の組織化手法としてはある程度まともかもしれない
- 失敗するたびに「アジャイルを正しくやらなかった」と言うアジャイル専門家には警戒すべきだとする
- 成功にはプロトタイプ、ツール、優れた開発者の採用のほうが重要である
- アジャイルプロセスは役立つかもしれないが、真に受けすぎると害になることがある
- no silver club、つまりすべてのソフトウェア問題を解決する銀の弾丸は存在しない
- Chesterton’s Fence は既存コード削除への警告として使われる
- ある柵の用途が分からないなら、まず理解すべきであり、すぐに取り払ってはいけないという内容である
- 見た目の悪いコードでも、今日動いているなら尊重すべきである
- 特に大規模システムほど、まず理解してから改善すべきである
- テストは、なぜある「柵」が存在するのかを教えてくれる手がかりになりうる
ツールと型システム
- ツールは、開発者が自分で記憶し推論しなければならない負担を減らしてくれる
- 新しい環境に入ったら、周辺ツールを学ぶことに時間を使うほうが生産性を高められることがある
- ドキュメントがなければ、他の開発者に聞きながら把握する必要があるかもしれない
- IDE の コード補完 は、API をすべて覚えていなくてもよくしてくれる
- Java プログラミングはコード補完なしではほとんど不可能だと表現している
- 優れたデバッガは非常に重要である
- 条件付きブレークポイント、式評価、スタック探索といった機能を深く学ぶべきである
- 新人開発者にとって、デバッガ学習は大学の授業よりもコンピュータについて多くを教えてくれることがあると考えている
- 型システムの最大の価値は「ドット (.) を押したときに何ができるかが表示されること」である
- 型の正確性も良いが、グラッグにとってはツール支援とコード補完のほうが大きな価値である
- 過剰な型抽象化やジェネリクスは、実務コードを難しくすることがある
- ジェネリクスは主にコンテナクラスに限定して使うほうを好む
式、DRY、関心の分離
- 短いコードより デバッグしやすいコード を好む
- 複雑な条件式を1行で書くより、中間変数に分けたほうが各式の結果と意味を見やすい
- 行数が増えても、条件分岐の理解とデバッグがしやすくなる
- DRY は良い助言だが、バランスが必要である
- 単純で明確な重複コードは、複雑なコールバック、クロージャ、オブジェクトモデルより良いことがある
- 重複を排除しようとして複雑性が増すなら損になることがある
- Separation of Concern にはより批判的である
- Web 開発の典型例は CSS、HTML、JavaScript の分離である
- グラッグは代わりに locality of behavior を好む
- 振る舞う対象の近くに関連コードを置けば、その対象を見たときに何をするのかすぐ分かる
- クロージャは、コレクション処理の抽象化のような正しい用途には有用である
- しかし塩、型システム、ジェネリクスのように、少し使うだけで十分であり、過剰になると有害である
- JavaScript の「callback hell」はクロージャ乱用の例として挙げられる
ロギング、並行性、最適化
- ロギングは、特にクラウド配備環境で非常に重要である
- 主要なロジック分岐ごとにログを残す
- リクエストが複数マシンにまたがるなら、すべてのログに request ID を入れてひも付けられるようにする
- 可能ならログレベルを動的に調整する
- 可能ならユーザー別のログレベルも調整する
- Java のロギングライブラリは複雑になりがちだが、ロギング基盤にきちんと投資すれば後で大きな見返りがある
- 並行性は恐れるべき対象である
- 可能な限り、状態を持たない Web リクエストハンドラと独立したリモートジョブキューのような単純なモデルを使う
- optimistic concurrency は Web 領域でうまく機能すると考える
- thread local variable は主にフレームワークコードを書くときにときどき使う
- Java の ConcurrentHashMap のような並行データ構造も、依然として注意して使うべきである
- 最適化は、実際の性能プロファイルがあるときに始めるべきである
- 「premature optimization is the root of all evil」に同意している
- 実際のボトルネックは予想と異なることがある
- CPU だけを見るべきではなく、ネットワーク呼び出しは数百万 CPU サイクルに相当しうるため、可能な限り減らすべきである
API、パース、Visitor パターン
- 良い API は、開発者にあまり考えさせない
- 悪い API は、実装内部やドメインモデル中心に設計されていたり、過度に抽象的だったりすると生まれる
- 単純な場合には単純な API を、複雑な場合にはより複雑な API を提供する 階層化 を好む
- オブジェクト指向 API では、機能を対象オブジェクトに付けるほうがよいと考える
- Java でリストをフィルタリングするために Stream に変換し、再び List に収集し直す流れを悪い例として挙げる
- よくある操作である
filter()は list にあり、list を返すべきだと考える
- パーサは recursive descent が楽しく美しい方式である
- パーサジェネレータは理解しにくく、デバッグしにくいと批判する
- 実際の本番用パーサはほぼ常に再帰下降方式だと主張する
- Bob Nystrom の Crafting Interpreters を勧めている
- オンラインで無料で読めるが、本の購入も勧めている
- ただし visitor pattern は罠だと表現している
- Visitor pattern への評価は短く「bad」である
フロントエンド、マイクロサービス、流行
- マイクロサービスは「システムをうまく分割するという最も難しい問題に、ネットワーク呼び出しまで追加する」点で危険である
- フロントエンド開発では、複雑性が特に強く現れる
- 単純なフォーム保存やブロシュアサイトにまで SPA ライブラリ、GraphQL JSON API、HTTP バックエンドが使われる状況を批判している
- フロントエンドとバックエンドを分けると、複雑性の住みかが2つできると表現する
- グラッグは複雑性を下げるために htmx と hyperscript を作ったと明かしている
- 単純な HTML を保ち、JavaScript をあまり使わない方向を好む
- React は就職や特定種類のアプリケーションにはより良いかもしれないと認めている
- 開発には流行が多く、特にフロントエンドでそうだと見る
- バックエンドはより退屈になり、多くの悪いアイデアはすでに試されたと表現する
- 革命的な新アプローチには、ひとつまみの塩を加えるように慎重に向き合えと言う
- 多くのアイデアはすでに一度は試されており、使い回された悪いアイデアが時間を浪費させることがある
恐れとインポスター症候群
- シニア開発者が公然と「これは複雑すぎる」と言うのは良いことである
- 開発者は Fear Of Looking Dumb(FOLD) のため、理解していないと言い出しにくい
- シニアが先に認めれば、ジュニアも複雑さや理解不足を口にできるようになる
- FOLD は、とくに若い開発者にとって複雑性が力を得る主要な源泉である
- ユーモア感覚と過去の失敗事例を覚えておく姿勢が役立つと考える
- インポスター症候群も開発ではよくある
- グラッグは、自分がすべてを支配している感覚と、自分が何をしているのか分からない感覚の間にいると表現する
- htmx と _hyperscript の オープンソースでの成果 があっても、今なおミスや失敗を恐れているという
- みんながインポスター症候群を感じているなら、誰も本当の偽物ではない、と受け止めるほうがよいと考える
おすすめの読み物と結論
- おすすめの読み物
- 最後の結論はシンプルである
- 複雑性はとても、とても悪い
1件のコメント
Hacker News のコメント
Carson 教授がこのコメントをご覧になっているなら、これまでのすべての貢献に心から感謝を伝えたいです。
大学でなぜ HTMX を学ぶのか、なぜあんなに熱心だったのか当時は分かりませんでしたが、数年たった今なら理解できます。すべては HTML over the wire だったのです。
Staff Ruby on Rails Engineer として働く中で、Hotwire における教授の仕事を見ましたし、Hacker News に時々登場したり、GitHub で Hotwire 開発者たちと会話している姿を見るのも素晴らしかったです。プログラミングコミュニティの光のような存在として、大いに尊敬され、感謝されています。
「優れたデバッガは輝く石ほど価値があり、実際にはそれ以上に価値がある」という言葉に同意します。
小さなスタートアップと「エリート」なビッグテックのチームの両方を経験しましたが、チームで デバッガ を使っているのはほとんどいつも自分だけでした。現実世界、少なくとも Web 技術の領域では、ほとんどの人が
print文でデバッグしているようです。テスト実行中に興味深いコード行で止め、そこまで到達した コールスタック を見ることは、頭の中でコードを順方向に実行してみるよりはるかに簡単です。若い grug にとってこの技術は小さな超能力なので、可能なら自分のコードベースで動くようにするために時間をかける価値があります。
彼らは、スタックトレースや変数を1つ2つ見る程度を超えるデバッガの使用はあまりしないと言っており、複雑なデータ構造や制御フローの詳細にはまり込みやすく、重要な箇所に出力文と自己検証コードを入れる方が生産的だと考えていました。
私もおおむね同意します。私がやるほぼすべての作業では、仮説-ログ-実行 の反復の方がずっと早く答えにたどり着けます。頭の中でコードを実行しようとしているのではなく、すでにコードがどう動くかについての作業モデルがあり、そのモデルが正しければどんな出力になるべきか分かっていて、間違った出力から実際の状況を素早く直感できるのです。
[0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
マイクロサービスメッシュ 構造ではローカルで何かをまともに実行すること自体が難しく、テスト環境もステップ実行デバッガをアタッチできるように設定されていないことが多いです。結局
printデバッグしかなく、ロギングシステム自体に問題があったり、ログを flush する前にプログラムが死んだりすると、それすら使えません。すぐに必ず必要になります。ところが共同作業中にも、デバッガの使い方そのものを知らない人たちを見て驚きました。「そこにブレークポイントを置いて」「次は関数の中に入って変数の状態を見て」「それはステップオーバーして」と言っても、毎回ぽかんとした表情が返ってきます。
厳密に言えば、
print文を入れて消すのではなく、残し続ける ロギングコード を入れます。主要なインターフェースなら、たいてい INFO レベルで関数の開始/終了と引数の値を記録するところから始め、システムを使う中でさらに調べるべき場所が見えたら、より詳細なログを追加します。ログ形式にもかなり力を入れます。分散システムで作業していたとき、各ログ行のプレフィックスを正確にそろえることが非常に有用でした。ノード ID、pid、タイムスタンプをすべて固定幅で入れてクラスタ全体のログをダウンロードしてソートすると、複数ノードの動作が1つのファイルに interleave された形で見えました。
しかしデバッガが自分の使っている ライブラリやフレームワーク の奥深くに入り込んだ瞬間、道に迷って嫌になります。そのフレームワークやライブラリは何万人時もの作業が積み重なったもので、自分のレベルをはるかに超えているように感じます。
ここには宝石のような文がたくさんありますが、マイクロサービスについてのこの一文がいちばん好きです。「grug は、なぜ賢い人たちがシステムを正しく分割するという最も難しい問題を持ち出して、そこにネットワーク呼び出しまで入れるのか不思議に思っている」
フォームが5つほどある些細なWebアプリを喜んで「マイクロサービス」に分割し、同じデータベースを共有させ、API管理レイヤーと大量(メガバイト)のバッチ処理用キュー、メール通知システム、自作のオブザーバビリティ基盤まで付けたうえで、「そのほうが簡単だ」と言って単純なWebフォームをSPAに変えるのです
いまでは「アーキテクチャ」と「パターン」が、役に立たない開発者のための雇用創出プログラムだと理解しています。そうでなければ、道端で「サンドイッチをくれたら JavaScript 書きます」という看板を持って立っていそうです
そういう人たちにとっては、API呼び出しとして公開されていなければ、理解も再利用もできない不透明なコードの塊でしかありません
複数のパッケージが内部で協力しつつ、コードベースの残りには小さなAPIだけを公開できます。ネットワークであるという事実が、モジュール同士にコールバックや振る舞いではなくデータだけをやり取りさせ、インターフェースを後方互換な形で進化させる圧力をかけます。そのため、異なるモジュールを異なるタイミングで「ホットリロード」しても爆発しないようにできます
実際のネットワークホップなしでも大部分は得られそうですが、真剣な試みはまだ見たことがありません
K8S のようなオーケストレーターなしでは実行できず、インストールや運用が難しいため、マネージドクラウドの販売に有利です。ネットワーク帯域とCPUをより多く使わせ、そのどちらも課金対象です
アプリケーション内部で複雑または大きな状態を共有・維持しにくくし、マネージドデータベースやイベントキューサービスを代替として使わせます。モノリスならキューやチャネルを使えば済みますが、マイクロサービスではKafkaのような巨大な獣が欲しくなります
ローカル実行も難しくなり、クラウド開発環境が必要になり、開発・テスト環境を複数持つ必要が出ることもあります。特定クラウドのネットワーキング方式のような特性に依存するようになり、クラウドロックインも強まります
クラウドがITコストを削減してくれると売り込まれていた時代を覚えているかは分かりません。笑える話でしたし、2000年代からでたらめだと分かっていて、結局あらゆるコストをさらに膨らませるだろうと思っていました
そのやり方のほうが管理しやすいからで、技術的な決定というより開発組織のあり方に近いものです。代替案がモノレポなら、個人的にはそちらのほうがもっと悪いと思います
「複雑さとティラノサウルスとの1対1のどちらかを選ぶなら、grug はティラノサウルスを選ぶ。少なくとも grug にはティラノサウルスが見える」
この文を少なくとも週に一度は思い出します
この grug は透明なティラノサウルスと1対1の最中で、呪われています
この記事の価値の一つは、より洗練された複雑なことができる人が、経験上あえて避けようとしている点にあります
洗練やより高い抽象化が必要な時と場所は確かにあります。しかし grug 哲学は、そうしたことをすること自体に本質的な価値があるわけではないと言っており、かなり妥当な助言に見えます
AI支援も、一貫していて平凡で、データ中心のコードのほうがより効果的だという点を見てきました。状況によるかもしれません
初心者開発者は単純なコードを書き、中級開発者は複雑なコードを書き、熟練開発者は再び単純なコードを書きます
どの講義を当然受けたものと前提にできるかは、状況によって異なります
現代のソフトウェア開発における皮肉の一つは、「結局は時間を節約してくれるはず」だと思って複雑性を導入してしまう点
ときには正しく、実際に時間を節約できるが、常にそうとは限らず、頻繁にそうとも限らない
DRYは、ときに早すぎる抽象化につながる。「このパターンは他の場所でも使われるはずだから、共通部分を抜き出そう」と考えた瞬間、複雑性の悪魔が入り込む
できるだけ多くのバグをコンパイル時に捕まえたいと思うが、そのためにはコンパイラが私たちが実際にやろうとしていることをより多く知る必要があり、理解しやすさを削る複雑な型を作ることになる
ボイラープレートを避けようとして複雑なマクロやDSLを作るが、漏れのある抽象化の法則のせいで、実際の実装を知らなければならない瞬間に頭が爆発する
これらの例が難しいのは、いずれも時には良い考えだという点だ。いつ複雑性を導入すれば単純化になるのかを判断できることが、優れたソフトウェアエンジニアの証だと思う
ビジネスロジックは理想的には一か所で定義されるべきだが、それ以外のものは必要なら重複してもよく、それ自体が悪いわけではない
DRYを調整するために「3の法則」も強調している。コピー&ペーストされたコードは3回までは問題なく、その後で抽象化を考えてもよい。もちろん、あらゆる場合に当てはまる経験則はなく、その感覚を教えるのは難しい
通常はFlaskのようなSinatra系フレームワークでWebアプリを作り、URLパターンに応答する関数を書く。一つの「画面」は、連携して動作する一つ以上の関数と関連するHTMLテンプレートで構成されることがある
アプリケーションがデータベース接続、ファイルの場所、HTMLのヘッダーやフッターのようなものを設定するなら、それ以外では画面同士の結合はほとんどない。新しい画面が必要なら既存の画面をコピーして直すか、LLMに画面やエンドポイントを作らせることができ、結果が悪ければ作り直せばよい
以前の職場で、MLの学習セットを作る Themis というフレームワークを作っていたが、マイクロサービス、React、Dockerなどを使っていた。実際の要件は新しい作業を継続的に追加し、作業ごとに単純だが非常に最適化された画面を素早く作ることだった。2万回判断しなければならないのに、1回ずつクリックするのも大変で、4回ずつクリックするなら8万回になって諦めることになる
当時の構造では、JAXBアプリケーションのAPIエンドポイントとモノリシックなReactアプリのコンポーネントを書き、TypeScript・Docker・javacが20分間走るのを待たなければならなかった。運がよければ起動し、そうでなければ最初からやり直しだった
Themisへの批判を書き、新しい作業の高速な開発を目標に Nemesis を設計したが、以前の職場では選ばれなかった道だった。それでもNemesisと私は、その後何百万もの作業インスタンスを処理してきている
一か所でしか使わないヘルパーを抜き出せという意味ではない。ロジックが一つの関数・クラス・ファイルなどに大量に入っていても、コピーされたものでなければ依然としてDRYだ
早すぎる抽象化は実際に存在する。CSの授業が概してそうするよう教えるのも助けにならない。新人にMySQLデータベースを渡すと、真っ先にMySQLを抽象化しようとするかもしれない
複雑であれ単純であれ、結果が価値を加えないなら重要ではない。まずは削ることよりも、より多くの価値を加えることに集中し、複雑性はその次に心配すればよい
好きなLLMの活用法の一つは、このエッセイを入れて、grug-brained developer のペルソナで、いま自分が扱っている課題についてコメントしてもらうこと。ストレス解消に良い
プロンプトを「このエッセイのGrug Brained Developerのように振る舞ってください」のように始めればいいのか気になる
「複雑性は非常に悪い」は本当にそのとおり
ソフトウェアエンジニアリングに携わってきたすべての年月を通じて、この考えはどんな状況でも一貫して真であることが示された数少ない原則の一つだった。問題の中には本質的に複雑なものもあるが、そういう場合でさえ、時間をかけて最も単純な解決策にたどり着くほうがずっとよい
自分の最も効果的だった仕事は、以前のアプローチを疑い、大胆に単純化した後に生まれた。潜在的な柔軟性を少し失うことはあるが、たいていの場合、必要だと思っていたほどの柔軟性は実際には必要なかった
例えば、かなり優秀でエージェント的に動作するLLMが登場してからは、壊れやすくデバッグしにくい過度に複雑なTypeScript型を避け、仕様のようなコードを書いたうえで、LLMにそれを基にしたコードを静的に生成させるようにしている
プロジェクトの ESLint 依存関係は、バージョン更新後に何度も壊れ、多くのルールは誤検知を避けられるほど洗練されておらず、TypeScriptとVSCodeで適切に維持するのも複雑だった。Biome.jsに変えるとより単純で十分に効果的だったが、最近はバグが出てきている。それでも、リンティングはあれば便利なものではあるが、過剰な時間をかけて面倒を見る対象ではないと気づき、ビルドツールチェーンから外したし、VSCodeでも常に有効にしておく必要はなくなった。たまにBiomeを実行してコードスタイルとフォーマットだけ確認すればよい
プロジェクト用のカスタムデータマイグレーションツールを作る際、順方向マイグレーションは必要だが、逆方向マイグレーションは実装にかかる時間と複雑さに見合わないと判断した。データの入ったデータベースをロールバックする必要があるならバックアップを復元すればよく、データがないか本番DBでないなら、バージョン付きの初期化スクリプトでクリーンな状態から始めればよい
3つ目がどう単純なのかもよく分からない。数学的な頭で考えれば、全単射の空間は簡単に作れる。別の手段で逆方向マイグレーションをまねるほうが難しい場合もある。もちろん細部によるので、一般的な規則ではない
賢いRichは complect とは互いに結び付けることだと言っていて、それには同意する。Richは複雑性は悪いと言ったが、それには同意しない。何かを結び付けることは必要だ。互いにつながっていなければ問題は解けない
この記事が2022年の記事だというのが信じられない
10年前に読んでいて、その時点ですでに古典だったと自信を持って言っていた気がする
悲しいがそのとおり。「yes」を覚えた次に、失敗したら他のgrugのせいにする方法を覚えるのが理想的なキャリアアドバイスだ
初めて企業の世界に入ったときは、これは事実ではなく、技術チームのコミュニケーションが不足しているだけだと思っていた。自分が間違っていて、grugが正しかった