（自分用）再利用性の落とし穴？Streamlit製ECモックアプリで学ぶ依存と柔軟性のバランスを考える

はじめに
--
アプリケーションを構築する際、再利用性の高い設計を心がけるのはエンジニアの常識ともいえます。
しかしその再利用性が「柔軟性の低下」や「変更への脆さ」を引き起こす可能性も考慮する必要がある。
本記事では、PythonのStreamlitを使って作った小さなECサイト風モックアプリを題材に、**「抽象化による恩恵」と「密結合のリスク」**を比較検討してみる。

実装サンプル：Streamlit製ECモックアプリ
--
今回のモックでは以下のような機能を想定しています。

商品名、価格、在庫数を表示
ボタンで「カートに入れる」操作
商品情報はJSONで管理
商品は最大4個表示

```python:example.py
import json
import streamlit as st

st.set_page_config(layout="wide")

class Product:
    def __init__(self, product_name, product_price, stock, id):
        self.product_name = product_name
        self.product_price = product_price
        self.stocks = stock
        self.id = id

    def create_product(self):
        st.image("商品.png")
        st.metric("商品名", value=self.product_name)
        col_1, col_2 = st.columns(2)
        with col_1:
            st.metric("価格", value=self.product_price)
        with col_2:
            st.metric("在庫数", value=self.stocks)
        if st.button("カートに入れる", key=self.id):
            st.write("カートに入れました")

class Shelf:
    def __init__(self):
        self.columns = st.columns(4)

    def create_shelf(self):
        return self.columns

# JSON読み込み
with open('product.json', encoding='utf-8') as f:
    data = json.load(f)

products = [Product(**item) for item in data]

st.title("ECサイトモック")
shelf = Shelf().create_shelf()
for col, product in zip(shelf, products):
    with col:
        product.create_product()
```

```json:product.json
[
  { "product_name": "バナナ", "product_price": 200, "stock": 50, "id": 1 },
  { "product_name": "いちご", "product_price": 500, "stock": 30, "id": 2 },
  { "product_name": "ぶどう", "product_price": 300, "stock": 80, "id": 3 },
  { "product_name": "レモン", "product_price": 80, "stock": 300, "id": 4 }
]
```
一見スッキリしているが…
--
このコードは、一見「いい設計」に見えます。
商品のUIは Product クラスにまとめられ
レイアウトも Shelf クラスで整理され
商品追加は JSON に追記するだけで済む
しかし、実際に機能拡張や要件変更が入ったとき、密結合な構造が柔軟性を損なう場面も想定しておく必要がある。

再利用性が高いことで起こる問題点
--
① JSON構造変更への脆さ
```python:example.py
products = [Product(**item) for item in data]
```
→ item に stock が存在しないだけで TypeError。この書き方は便利な反面、構造変更に弱い。

② UIロジックが Product クラスに固定
商品のUIが create_product() にべったり
表示形式を変えるにはクラス自体を修正

商品画像は "商品.png" にハードコード
（モックのため、今回に限り許容）
→ 表示を「シンプル版/詳細版」で切り替えるには構造の再設計が必要。

③ 「カートに入れる」など副作用が内包
ボタン押下による処理（状態変更）がクラス内に含まれる
テストや状態管理の切り出しが困難

改善案：データとUIの分離、@dataclass の活用
--
Python 3.7以降であれば、データ構造には dataclasses モジュールが便利です。

Product をデータクラスにする
```python:example.py
from dataclasses import dataclass

@dataclass
class Product:
    product_name: str
    product_price: int
    stock: int
    id: int
```
UIは別の関数にする
```python:example.py
def render_product(product: Product):
    st.image("商品.png")
    st.metric("商品名", value=product.product_name)
    col_1, col_2 = st.columns(2)
    with col_1:
        st.metric("価格", value=product.product_price)
    with col_2:
        st.metric("在庫数", value=product.stock)
    if st.button("カートに入れる", key=product.id):
        st.write(f"{product.product_name} をカートに入れました")
```

Main 部分は以下のようにシンプルに

```python:example.py
with open('product.json', encoding='utf-8') as f:
    data = json.load(f)

products = [Product(**item) for item in data]
cols = st.columns(4)

for col, product in zip(cols, products):
    with col:
        render_product(product)
```

データと処理の分離がもたらすもの
--
| 項目     | クラス統合型（元）  | 分離型（改善）   |
| ------ | ---------- | --------- |
| 再利用性   | 高いが過剰      | 高い（適切）    |
| 拡張性    | 低い（UI修正困難） | 高い（関数単位）  |
| テスト容易性 | 低い         | 高い（副作用分離） |
| 柔軟性    | 低い         | 高い        |

結論：再利用性はバランスが大切
--
クラスや関数で共通化・再利用を進めることは、コードをDRYに保つために不可欠です。
しかし、再利用性が高すぎる設計は、変更への耐性を下げることにもつながります。特にUIとロジックが密結合したクラス構成では、ちょっとした表示変更が大改修につながることも。
「どこまで共通化し、どこに自由度を持たせるか」 — そのバランス感覚を持つことが大切である。