ファイルにclose()漏れは構造の問題だった──STSで責任を見せる設計

# はじめに
AIのコードでも事故る──それは“責任の構造”が見えないからかもしれません。
「ファイルを開いたのに閉じてない」──  
そんな初歩的なバグから、設計構造の大切さに気づいた話です。

AIの指摘通りに修正したつもりだった。  
でも、師匠に見せると「設計として破綻してる」と一刀両断。

この記事では、そんなクローズ漏れのコードを  
入力・変換・出力の責務で分けるSTS（Source / Transfer / Sink）構造に沿って見直した過程を紹介します。

結果、コードの品質もレビューのしやすさも大きく向上しました。

# 💥 実例：AIが提案したけどミスがあったコード
最初に書かれたのは、こういうコードでした。

```
function run() {
  const f = open("users.txt");
  const line = f.readLine();
  if (!line) {
    // ✅ この分岐では閉じている
    f.close(); 
    return;
  }

  // 　色々と処理

  if (line === "SKIP") {
    // ❌ close() を呼ばずに return
    return;
  }
  const user = transform(line);
  f.close(); // ✅ 正常系もクローズ
  output(user);
}
```

Line === "SKIP" のときだけファイルが開きっぱなしになる設計ミス。

AIでレビューしてもらうと、確かにの漏れには気づかせてくれた。  
だが、今回はクローズ処理の位置が数か所だったから気づけただけで、  
もし責務の範囲が広がっていたら、そこまで深くレビューするのは難しかった。

AIは条件式の分岐や警告には反応できるが、「責務が構造的に分かれていない」といった抽象的な設計ミスには気づけないことが多い。

つまり、AIは部分的な不具合発見には強いが、構造全体の整合性を見るのはまだ不得意だということを痛感した。

コードを整えて「完了」と思った私は、以前から相談に乗ってもらっている先輩エンジニア（以下、師匠）に見てもらうことにした。

#  師匠の一言：「構造で責任が見えないのが問題なんだよ」
私が AI の提案どおりに修正したコードを見せると、  
師匠は静かに、でもはっきりと言った。

「責任が“構造”として明示されていないコードは危ないんだよ」  

「どのルートを通っても、必ず閉じる」  
その保証がコードから“構造として見える”必要がある。

師匠が言っていたのは、「閉じる処理がすべてに書かれているか」ではなく、  
「その責任がどこに、どう構造として存在しているか」が見えることが大事だということだった。

たとえ close() をどこかで呼んでいたとしても、  
それが if 文の中や別関数に散らばっていたら、  
レビューのときに「本当にすべてのパスで閉じてるか？」が分からなくなる。

設計とは、構造で責任を保証すること。  
この一言が、後々の再設計の土台になった。
そして、STS（Source / Transfer / Sink）構造で作り直すこととなった。

では、今回のコードをどのように再設計したのか？ その鍵となったSTS構造について簡単に紹介します。

# STS構造（STS設計）とは？

STSという設計思想は、特に新しいものではありません。  
実は、COBOL時代の業務システムなどでも自然に使われていた構造であり、昔のメインフレーム処理でも「入力 → 変換 → 出力」という分かりやすい流れが設計の基本でした。

時代は変わっても、この“責務を意味で分ける”という原則は今でも強力に有効であり、  
**技術や構文が進化しても、人が設計を見るときに必要なのは“流れと責任の見通し”**です。

この設計は、今回のようなファイル処理に限らず、  
「入力・変換・出力」があるすべてのロジックに応用できる再現性の高い構造です。

AIにも構文にも頼らず、構造で責任を保証すること。  
それがSTSの本質です。

| 区分     | 意味・役割               | 例（今回のコード）           |
|----------|--------------------------|------------------------------|
| Source   | 入力・取得・受け取り     | `open()`, `readLine()`       |
| Transfer | 判断・変換・加工         | `transform(line)`            |
| Sink     | 出力・保存・破棄         | `close()`, `output()`        |

# STS構造で再設計したコード

```
function run() {

  const f = open("users.txt");         // Source（入力） → Sinkのclose()と対構造を形成
  const line = f.readLine();           // Source（入力）

  const user = transform(line);        // Transfer（変換）
  
  f.close();                           // Sink（対構造を明示）
  output(user);                        // Sink（出力）
}
```

責務の流れが上から下に一貫して並び、  
開いたら必ず閉じる構造が目で見て確認できるコードになった。

「開いたら必ず閉じる」という動作は、  
多くのプログラミング言語で構文的に保証する方法が用意されています。

たとえば、Pythonの `with` 文、Javaの try-with-resources、Goの `defer`、RustやC++のRAII などがそれに当たります。

ですが今回は、それら言語機能に依存せず、設計の構造そのものによって「開いたら閉じる」が保証されるような形を目指しました。

オープンとクローズが責務の対構造として明示されていることで、  
人間が見ても、AIが処理しても、「開いたら閉じる」が構造上保証されていることが一目で分かる。  

この構造によって、責任が明確になり、コードの可読性と保守性が大きく向上しました。
特にレビューでは、「パスごとにクローズされているか？」を個別に追う必要がなくなり、確認の負荷が大きく減りました。

# レビューとテストの楽さ

実際にレビューする際も、クローズ漏れがないことが、ひと目でわかる。

それだけでなく、機能拡張する際も、責任が明確なので、どの部分を修正したらいいか一瞬で決まる。また影響範囲も明確なので、テストも楽になった。

責任が構造に沿って並んでいることで、コードの読みやすさ・レビューの確実さ・テストのしやすさすべてが劇的に改善された。

あなたのコードの中で、「入力・処理・出力」が混在している箇所があれば、  
ぜひ一度STSで責務を分けてみてください。構造の見通しが変わり、レビューも保守も楽になりました。

# おわりに

ここまでお読みいただき、ありがとうございました。  
本記事では、AIにコードを書かせたものの、命名や設計意図が曖昧だったことで伝わらず、バグが生まれた事例を通じて、STS設計の力を紹介しました。

設計とは、動けばいいコードを書くことではなく、  
“責任の位置”が見える構造を作り、他の人に「伝わるコード」にすること。

次回は、チームメンバーにSTS設計を伝える際に起きた出来事──  
コードでは伝わらなかったが、“WHATの図”で一瞬で伝わったという経験を紹介します。  
設計レビューやテスト設計で悩んでいる方には、きっと参考になるはずです。

-----

また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。

## Udemyコース（8,800円 → クーポンで割引中）

▶️ [AIとC#で極める！クリーンコードの技法（限定クーポン付き）](https://www.udemy.com/course/aic-hjke/?couponCode=C49C4B16FBE249DDFFDA)

- C#でクリーンコードと設計力を身につける実践講座  
- ChatGPTの活用方法や、伝わるコードの考え方を解説

## 出版書籍『あきらめない者たち』

▶️ [Amazonで見る](https://amzn.asia/d/dK37sIu)

- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのかのノンフィクション作品です。