2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

42Tokyoで知った、エラー処理は「後付けできない設計」だった話

2
Posted at

はじめに

42で1番沼ったminishellから学んだエラー処理と設計についてのお話です。

42で課題を進めていると、最初はこう考えがち(こんな感じでもなんとかなりがち)だと思います。

  • エラーが起きうることは分かっている
  • 必要そうなチェックは入れている
  • 足りなければ後から足していけばいい

私はまさにこんな状態でした。
しかし minishell で一度本気で詰んだことで、この考え方が「設計としては不十分だったと痛感しました。

この記事では、

  • エラー処理は「実装」ではなく「設計」だったこと
  • return / exit / 構造体に状態を持たせる判断
  • close(fd) の責任は誰が持つのか
  • ft_error_and_exit に行き着くまでの試行錯誤

を、minishell の失敗エピソードを交えて書きます。


エラー処理は「ある程度把握してから足していけばいい」と思っていた

正直に言うと、
「エラー処理はあとで考えればいい」とまで雑に考えていたわけではありません。

  • open / malloc / fork が失敗しうることは分かっている
  • return 値を見る必要があることも分かっている
  • 最低限のエラー処理は入れているつもりだった

なので当時の自分の感覚は、どちらかというとこうでした。

まず全体の流れを作る
想定できるエラーは入れておく
足りなかったら、あとから補えばいい

libft や fdf の規模感では、
このやり方でも「それなりに」成立してしまいます。

しかし minishell でこの姿勢のまま進めた結果、
エラー処理が「設計」ではなく「場当たり的な追加」になっていたことに気づきました。


「把握しているつもり」と「設計している」の違い

「把握しているつもり」と「設計している」の違いは、
何が失敗したときに、どこまで・誰が責務を持つのかを言語化できているかだと思います。

エラーが起きうることを理解していても、
「この関数はどこまで後始末をするのか」「ここで失敗したら上位に何を伝えるのか」「ここから先は絶対に戻らないのか」といった点を、
なんとなくの感覚で決めているうちは、それは設計しているとは言えません

minishell で詰んだ原因も、エラーそのものではなく、
責務の境界を曖昧にしたまま実装を進めてしまったことでした。
エラー処理を「把握している」状態から一段階進めて、
「どのレイヤがどこまで責任を持つのか」を明示できて初めて、設計として機能し始めるのだと感じました。


minishellで本当に詰んだ瞬間

minishell では、

  • パース
  • 環境変数展開
  • リダイレクト
  • パイプ
  • fork / exec
  • ファイルディスクリプタ管理

など、失敗しうるポイントが一気に増えます。

ある時、私はこんな状態に陥りました。

  • どこかで open() が失敗
  • fd が中途半端に残る
  • 次のコマンドで挙動がおかしい
  • 最終的に segfault、もしくは fd が枯渇

デバッグしても原因がなかなか分からない。

理由は単純でした。

誰が fd を close する責任を持っているのか、設計していなかった


close(fd) は誰の責任か?

当時の私は、

  • open した関数で close すればいい
  • エラーが出たらその場で close すればいい

と考えていました。

しかし実際には、

  • open した関数
  • その fd を使う関数
  • エラーで途中 return した関数

が入り乱れ、
「誰が後始末をするのか」が曖昧になっていました。

結果として、

  • 二重 close
  • close し忘れ
  • エラー時だけリーク

が頻発します。

ここで初めて気づきました。

close(fd) の位置は「実装の都合」ではなく「設計」で決めるべきだった


エラー処理は「制御フローの設計」

minishellで詰んだあと、私は考え方を変えました。

  • このエラーは return で上位に伝えるべきか
  • ここで exit すべきなのか
  • 親プロセスか子プロセスか
  • エラー状態を構造体に持たせるべきか

これらは関数を書き始める前に決めておくべきことでした。


ft_error_and_exit に行き着くまで

最終的に私は、エラー処理を次のように整理しました。

if (close(fd) == -1)
    ft_error_and_exit("ERROR: Failed to close file.\n", NULL);

重要なのは関数名ではありません。

  • この関数を呼んだら、もう戻らない
  • ここで全て後始末をする

という 責務を明確にした設計です。

  • どこで exit するのか
  • どこまで戻る可能性があるのか
  • どこでリソースを完全に解放するのか

を明示できるようになりました。


「誰が後始末をするか」を決めていなかった

minishell での最大の失敗はこれでした。

エラーが起きたとき、
誰が責任を持って後始末をするかを決めていなかった

エラー処理を後付けで足していくと、

  • if 文が増える
  • free が散らばる
  • 制御フローが追えなくなる

結果、コードは簡単に壊れます。


まとめ:エラー処理は設計そのもの

42Tokyoで学んだ一番大きな気づきの一つはこれです。

  • エラー処理は「実装」ではない
  • エラー処理は「設計」そのもの
  • 後付けはほぼ不可能

特に minishell のような課題では、

エラー処理をどう扱うか決めていない設計は、必ず破綻する

もし今、

  • エラー処理をとりあえず return で済ませている
  • close / free の責任が曖昧
  • exit の位置が場当たり的

であれば、一度立ち止まって、

「誰が後始末をする設計なのか?」

を考えてみてほしいです。

それだけで、コードの安定度は確実に変わります。

おまけ

minishellのレビューでは、普段使うことあるのか?!というようなコマンドで破壊されます🥺
そんな時、設計ができてるチームはいつもレビュー中に原因を特定し、綺麗なまま修正できているなと感じます。
レビューと修正を繰り返して煩雑なコードができあがらないように、初めから設計を丁寧に行いたい所存です。


42生としてクリスマスを迎えるのは今年で3回目ですが、はじめてアドベントカレンダーに参加できて嬉しかったです。ありがとうございました🎄

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?