見出し画像

🎉 初心者のアプリ制作100日チャレンジ【Day77】エラー・空状態に対応する〜「壊れにくいアプリ」に仕上げる〜

💡 はじめに

Day76で、
検索・絞り込みまでできるようになりました。

ここまで来ると、
アプリはもう 8割完成 しています。

残りの2割は何か。

それが
「うまくいかなかった時にどう振る舞うか」 です。


🎯 今日のゴール(Day77)

  • 未入力・失敗時に適切な表示ができる

  • データ0件でも違和感がない

  • ユーザーが不安にならないUIになる

  • 「業務で使える」安心感を出す


❌ よくある惜しいアプリ

  • 何も起きない

  • 画面が真っ白

  • エラーがそのまま表示される

作った本人は分かるけど、
使う側は不安になります。


🧠 今日の考え方(超重要)

今日のキーワードはこれです。

「失敗は想定内。沈黙はNG。」


① 未入力エラーを“ちゃんと伝える”

❌ ありがちな書き方

if submitted:
    insert_drug(name, memo)

これだと
空文字でも保存しに行きます。


✅ 改善した書き方

if submitted:
    if name.strip() == "":
        st.warning("薬品名を入力してください")
    else:
        insert_drug(name, memo)
        st.success("DBに保存しました")

ポイント:

  • strip() で空白だけも防ぐ

  • warning はエラーより優しい

  • 何をすればいいかが分かる


② データ0件でも「壊れてない」表示

❌ 何も表示しない

if rows:
    st.table(rows)

これだと
「壊れた?」と思われます。


✅ 空状態を明示する

if len(rows) == 0:
    st.info("まだデータは登録されていません")
else:
    st.table(rows)

これだけで
安心感が段違い です。


③ 検索結果が0件の時

Day76の検索に
ひと工夫します。

if search_word and len(rows) == 0:
    st.info("検索条件に一致するデータはありません")
elif len(rows) == 0:
    st.info("まだデータは登録されていません")
else:
    st.table(rows)
  • 検索した結果0件

  • そもそも0件

この2つを
ちゃんと分ける のがポイント。


④ DBエラーを画面に出さない

SQLiteは基本安定していますが、
万一に備えます。

def safe_insert_drug(name, memo):
    try:
        insert_drug(name, memo)
        return True
    except Exception:
        return False
if submitted:
    if name.strip() == "":
        st.warning("薬品名を入力してください")
    else:
        success = safe_insert_drug(name, memo)
        if success:
            st.success("DBに保存しました")
        else:
            st.error("保存に失敗しました。再度お試しください")

これで
生のエラーをユーザーに見せない


✅ Day77 完成コード(エラー対応込み)

Day76のコードを
これに置き換えてOK です。

import streamlit as st
import sqlite3
from datetime import date
import os

DB_DIR = "data"
DB_PATH = os.path.join(DB_DIR, "app.db")

def init_db():
    os.makedirs(DB_DIR, exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS drugs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            memo TEXT,
            created DATE
        )
    """)
    conn.commit()
    conn.close()

def insert_drug(name, memo):
    conn = sqlite3.connect(DB_PATH)
    cur = conn.cursor()
    cur.execute(
        "INSERT INTO drugs (name, memo, created) VALUES (?, ?, ?)",
        (name, memo, date.today())
    )
    conn.commit()
    conn.close()

def safe_insert_drug(name, memo):
    try:
        insert_drug(name, memo)
        return True
    except Exception:
        return False

def search_drugs(keyword):
    conn = sqlite3.connect(DB_PATH)
    cur = conn.cursor()

    if keyword:
        cur.execute(
            """
            SELECT id, name, memo, created
            FROM drugs
            WHERE name LIKE ?
            ORDER BY id DESC
            """,
            (f"%{keyword}%",)
        )
    else:
        cur.execute(
            """
            SELECT id, name, memo, created
            FROM drugs
            ORDER BY id DESC
            """
        )

    rows = cur.fetchall()
    conn.close()
    return rows

st.title("新薬情報 管理アプリ")

init_db()

with st.form("drug_form"):
    name = st.text_input("薬品名(必須)")
    memo = st.text_area("補足メモ(任意)")
    submitted = st.form_submit_button("登録する")

if submitted:
    if name.strip() == "":
        st.warning("薬品名を入力してください")
    else:
        if safe_insert_drug(name, memo):
            st.success("DBに保存しました")
        else:
            st.error("保存に失敗しました")

st.divider()

st.subheader("登録一覧")

search_word = st.text_input("薬品名で検索")

rows = search_drugs(search_word)

if search_word and len(rows) == 0:
    st.info("検索条件に一致するデータはありません")
elif len(rows) == 0:
    st.info("まだデータは登録されていません")
else:
    st.table(rows)

👀 何が良くなったか

  • 失敗しても沈黙しない

  • 状況が言葉で分かる

  • ユーザーが迷わない

この時点で
「業務に出せる最低ライン」 に到達しています。


🌱 今日のまとめ(Day77)

  • エラーは想定内

  • 空状態は必ず説明する

  • 沈黙よりメッセージ

  • 完成度は細部で決まる


🔮 次回予告(Day78)

次は、

UIの最終整理

  • 表示の順番

  • 見た目の整え方

  • 「触って気持ちいい」仕上げ

いいなと思ったら応援しよう!