YovStudio

article 第 6 章:実務で活きる「読む」

公開日: 2025-06-21著者: Yov in YovStudio

実務シーンを体験する

第 4 章や第 5 章で扱った「手を加える経験」や「読む力の育て方」をふまえて、今回はいよいよ「実務」での話です。

ここまで、プログラミングにおいて「読む」ことの意味や価値を、AI 時代の視点も交えながら掘り下げてきました。
私は普段実務において、「読める」ことで救われる瞬間がたくさんあります。コードを正しく理解することで、スピーディーに不具合の原因にたどり着けたり、改修の道筋を自信を持って描けたり。

ただ、経験が浅いうちは「このやり方でいいのかな?」と不安になる場面も多いと思います。そんなとき、どこに着目し、どう考えていけばよいか──

今回はちょっとしたアプリを題材に、「不具合の調査」と「機能の追加」という実務シーンを体験し、「読む力」が実務の中でどう役立つのかを一緒に探ってみましょう。

題材にするアプリ

今回の題材は、HTML/CSS/JavaScript で構成された簡単な TODO アプリです。 以下のような機能があります:

  • タスクを入力して追加
  • タスクを完了
  • タスクを削除
  • 完了したタスクも表示
  • 入力が空のときは追加できない

実際の動作はこちら:
公開中のデモページ

アプリのコードは以下の通りです。少し長いので折りたたみで掲載しますが、全文を読まなくても、後のケース紹介で必要な部分を抜粋しながら説明していきます。

アプリのコード全文
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Simple TODO</title>
    <style>
      .done {
        text-decoration: line-through;
        color: gray;
      }
      body {
        font-family: sans-serif;
      }
      input {
        padding: 0.5rem;
        font-size: 1rem;
      }
      input[type='text'] {
        width: 216px;
      }
      input[type='checkbox'] {
        transform: scale(1.8);
        margin-right: 0.7rem;
        accent-color: teal;
        vertical-align: middle;
        cursor: pointer;
      }
      button {
        padding: 0.45rem 0.95rem;
        margin-left: 0.5rem;
        font-size: 0.8rem;
        color: white;
        border: none;
        background: teal;
        cursor: pointer;
        border-radius: 5px;
      }
      ul {
        margin-top: 1rem;
        list-style: none;
        padding-left: 0;
      }
      li {
        margin: 0 auto 0.1rem;
        background: #efefef;
        padding: 0.5rem 0.5rem;
        width: 304px;
      }
      li input {
        vertical-align: middle;
      }
      li span {
        width: 206px;
        margin-left: 4px;
        vertical-align: middle;
        display: inline-block;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        text-align: left;
      }
      .container {
        text-align: center;
        width: 95svw;
        margin: 0 auto;
      }
      .input_wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 0.2rem;
        margin-bottom: 1rem;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Simple TODO</h1>
      <div class="input_wrapper">
        <input id="taskInput" type="text" placeholder="タスクを入力" />
        <button id="addButton">追加</button>
      </div>
      <label> <input type="checkbox" id="hideCompleted" /> 完了済みのタスクを隠す </label>
      <ul id="taskList"></ul>
    </div>

    <script>
      const taskInput = document.getElementById('taskInput');
      const addButton = document.getElementById('addButton');
      const taskList = document.getElementById('taskList');
      const hideCompleted = document.getElementById('hideCompleted');

      let tasks = [
        { name: '設計書執筆', done: true },
        { name: '設計レビュー', done: false },
        { name: '製造', done: false },
        { name: 'コードレビュー', done: false },
      ];

      function renderTasks() {
        taskList.innerHTML = '';
        tasks.forEach((task, index) => {
          if (hideCompleted.checked && task.done) return;

          const li = document.createElement('li');
          const checkbox = document.createElement('input');
          checkbox.type = 'checkbox';
          checkbox.checked = task.done;
          checkbox.addEventListener('change', () => {
            task.done = checkbox.checked;
            renderTasks();
          });

          const span = document.createElement('span');
          span.textContent = task.name;
          if (task.done) span.classList.add('done');

          const deleteButton = document.createElement('button');
          deleteButton.textContent = '削除';
          deleteButton.addEventListener('click', () => {
            tasks.splice(index, 1);
            renderTasks();
          });

          li.appendChild(checkbox);
          li.appendChild(span);
          li.appendChild(deleteButton);
          taskList.appendChild(li);
        });
      }

      addButton.addEventListener('click', () => {
        const text = taskInput.value;
        if (text === '') {
          return;
        }
        tasks.push({ name: text, done: false });
        taskInput.value = '';
        renderTasks();
      });

      hideCompleted.addEventListener('change', renderTasks);

      renderTasks();
    </script>
  </body>
</html>

Case1: 不具合が報告されたときの既存コード調査

ある日、こんな報告があがってきました。

「TODO アプリで空白のタスクが登録できてしまいます」

ユーザーが空欄で「追加」ボタンを押した場合、通常は何も起こらない、あるいはエラーメッセージが表示されるような動作を期待すると思います。しかしどうやら、タスクの内容部分に何も表示されない空のタスクがリストに追加されてしまうことがあるようで、添付画像も一緒に送られてきています。

添付画像: 不具合スクリーンショット

チームリーダーがあなたに、原因の調査と解決策の提案をお願いしたいとのことです。こっそり私がサポートするので、一緒に原因を探ってみましょう。

ステップ 1:事象の確認

さて、何が起こっているのでしょうか?

まずは同じ事象を再現できるか、簡単に試すことができるので確認してみましょうか。
公開中のデモページ

たしか報告によると「空白のタスクを登録できてしまう」のでしたね。

では何も入力せずに「追加」を押してみます。
── タスクは登録されませんね?バグじゃなさそう?

この結果で「再現性なし」と報告しようとしている方、少し待ってください。
私の経験では、画像が添付されている以上、なんらかの手順で必ず再現するはずです。

とはいえ、どうしたものか。
再現できないままコードを読むという手もありますが、再現方法がわかっているとコード読むときのあたりがつけやすくなるので、できるだけ再現手順を明確にしておきたいところです。

── あ。報告にある「空白のタスク」はもしかしたら「スペースやタブ」のことではありませんか?さっそく試してみましょう。

── ビンゴ!出ました、空白のタスク。やっぱりスペースやタブが犯人だったようです。つい先入観で「何も入力しない」を試してしまいましたが、「空白」の正体は見えない文字のようです。

ステップ 2:どこを読めばいいか見当をつける

では事象を再現できたことですし、コードを読んでいきましょう。
とはいえ、闇雲に読んでも効率よく原因を探せません。仮説を立てて読む箇所を絞りますよ。

再現確認の結果、以下のことが分かりましたね。

  • 何も入力しないときは追加されない(想定通り)
  • スペースを入力してみたら空白のタスクが追加された(想定外)

このことから、「タスク追加時に入力チェックをしているはずだが、不備がある?」という仮説が立ちます。

コードを読む

一応、他の原因である可能性もゼロではありませんが、納得できる仮説ができたのでコードを読んでみます。

追加ボタンを押したときの処理を探してみましょう。まずはアプリのコードの全体像をつかんでみます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Simple TODO</title>
    <style>
      (略)
    </style>
  </head>
  <body>
    <div>
      <h1>Simple TODO</h1>
      <div class="input_wrapper">
        <input id="taskInput" type="text" placeholder="タスクを入力" />
        <button id="addButton">追加</button>
      </div>
      <label> <input type="checkbox" id="hideCompleted" /> 完了済みのタスクを隠す </label>
      <ul id="taskList"></ul>
    </div>

    <script>
      (略)
    </script>
  </body>
</html>

アプリのコードは html で書かれていて、head タグ内にはページタイトルや style タグによるスタイル定義が、body タグには入力欄や追加ボタンといった部品と、script タグで何やら処理が書かれています。

<input id="taskInput" type="text" placeholder="タスクを入力" />
<button id="addButton">追加</button>

これが追加ボタンを表示している部分です。この部分には問題になりそうな箇所は見受けられませんが、id="addButton"とつけられていますので、さらにaddButtonを検索してみます。

<script>
  (略)

  const addButton = document.getElementById('addButton');

  (略)

  addButton.addEventListener('click', () => {
    const text = taskInput.value;
    if (text === '') {
      return;
    }
    tasks.push({ name: text, done: false });
    taskInput.value = '';
    renderTasks();
  });

  (略)
</script>

ありました、addButton.addEventListener('click', () => { (略) });という記述で、addButtonがクリックされたときの処理が定義されています。

確認してみると、以下の入力チェックをしています。

const text = taskInput.value;
if (text === '') {
  return; // 入力値が空の場合、何もせず終了している
}

このチェック、たしかに入力が空だった場合には何もしないようになっていますが、少し甘いことに気づきますか?スペースやタブだけが入力された場合、どうなるでしょう?

スペースやタブは空文字ではないため、このチェックには当てはまらず、登録処理に進んでしまうのです。

念のため後続処理も確認しておきますが、renderTasks()関数も含め、空白のタスクが表示されたという事象につながりそうな分岐も特になさそうですね。

なぜテストで気づけなかった?

通常、アプリを公開する前にテストをしますが、なぜテストの段階で気づけなかったのでしょうか。

この種の不具合は、画面の動作確認だけでは見落とされがちです。テスト観点として「スペースだけの入力」まで試すという発想がないと、単に「空文字で Enter を押しても何も起きなかった」だけで OK としてしまうことが多いからです。

実際、開発中の動作確認では「空欄 → 登録されない」までは確認していたものの、「スペースだけの入力 → 登録されるか」は見ていなかった、というケースはよくあります。

改修の方針

理想としては、空文字だけでなく、スペースやタブといった見えない文字だけの入力も無効として扱うのが良いでしょう。JavaScript にはtrim()という関数が用意されており、文字列の前後にある空白文字(スペースやタブ、改行など)を取り除いてくれます。これを使うのがよさそうです。

さて、具体的にどんな修正をしましょうか?

次のような方針が考えられます:

  • 入力値を受け取った時点で trim() をかける
- const text = taskInput.value;
+ const text = taskInput.value.trim();
if (text === '') {
  return;
}
  • チェック条件を text.trim() === '' に変更する
const text = taskInput.value;
- if (text === '') {
+ if (text.trim() === '') {
  return;
}

どちらを採用するかは、textの値をこのあとの登録で使用することに着目して決めます。

tasks.push({ name: text, done: false });

trim()した後の値をタスクとして登録したい場合は前者、trim()する前の状態でタスク登録したい場合は後者を選択します。

AsIs と ToBe

では調査結果を AsIs(現状)と ToBe(あるべき姿)にまとめて、チームリーダーに報告しましょう。

── どうでしたか?

このように、AsIs(アズイズ)を丁寧に把握したうえで、ToBe(トゥービー)に向けた小さな改善を提案できると、調査からの価値提供がぐっと実務的になります。

また、こうした不具合調査は、実務では非常によくあるシーンです。 最初は原因の見当もつかないかもしれませんが、現状を言語化しながら調べる癖をつけることで、少しずつ調査スキルが上がっていきます。

Case 2:機能追加による改修

次に、「タスクに優先度をつけたい」という要望が出てきたケースを考えてみましょう。

依頼内容

  • タスクの入力欄の横に優先度(例:高・中・低)を選べる UI を追加してほしい
  • 優先度ごとに表示の色を変えたい

おや、チームリーダーは今回もあなたにどんな修正をすればよさそうかのまとめをお願いしたいみたいですね。不具合調査の経験を活かして取り組んでみましょう!

AsIs(現状)の整理

まずは「今どんな構造になっているか」を読みながら整理するところから始めます。

タスクがどんな情報を持っているか確認してみましょうか。
タスクの追加処理にこんな記述がありますね。

tasks.push({ name: text, done: false });

これは新しいタスクをタスクの一覧に追加している処理です。textは入力欄から受け取ったタスク名で、完了したかどうかを示すdoneには登録時はfalseがセットされているようです。
現状はこの 2 つだけで、タスクには優先度の情報は存在していませんね。

次はタスクの入力欄を確認してみましょう。

<div class="input_wrapper">
  <input id="taskInput" type="text" placeholder="タスクを入力" />
  <button id="addButton">追加</button>
</div>

タスクを入力するテキストボックスと「追加」ボタンしかなく、優先度を設定することはできなさそうです。

「今の状態から変えたいところはどこか?」という目線で見ていくと、タスクの一覧を表示する処理にも着目する必要があります。目を通しておきましょう。

function renderTasks() {
  (略)
}

コードは長いので省略していますが、特に優先度にかかわる記述はなさそうです。

ToBe(あるべき姿)の検討

となると、以下の 3 点を中心に手を加える必要がありそうですね。

  • 入力時に優先度を指定できる部品の追加
  • タスクのデータ構造に優先度情報を追加
  • 表示処理に優先度を反映

たとえば、こんな風に変えていくのはどうでしょうか?

タスク入力時に優先度を設定可能にする

セレクトボックスを追加して“高・中・低”を選択できるようにするのが自然です。

こんなイメージです。たとえば初期値を「中」にしておくと、ユーザーが選び直さなくてもよいケースが多く、使いやすさにつながるかもしれません。

優先度:
<select id="prioritySelect">
  <option value="high">高</option>
  <option value="medium" selected>中</option>
  <option value="low">低</option>
</select>

タスクのデータ構造の修正

namedoneのほかに、優先度を示すpriorityを追加すると、表示の出し分けができそうです。
たとえば追加処理では以下のようなデータ構造になります。

 tasks.push({ name: text, done: false, priority: 'セレクトボックスの値' }) 

優先度ごとにタスクの色を変える

renderTasks()内に手を加え、タスクに設定された優先度に応じて表示スタイルを変更します。たとえば「高」は赤、「中」はオレンジ、「低」は白などです。CSS で.priority-high { color: red; }のようなクラスを用意して、タスクの優先度に応じて付け替えるとよいでしょう。
また、視認性を補うために、優先度ラベル(例:“[高]“)をタスク名の先頭に追加表示しておくのも一案です。

要望+ α を考える:優先度による絞り込みや並び替え

今回の要望には含まれていませんが、ユーザー視点に立って「あると便利そうな機能」を考えてみるのも開発者の大事な視点です。

たとえば、優先度「高」だけを表示できたら便利そうではありませんか? もしくは優先度順のソートでもよいです。

たくさんタスクを登録するユーザーにとって、優先的に取り組むべきタスクが埋もれてしまう状況は不便なはずで、絞って表示したくなるはずです。

もしこの機能も追加するなら、フィルタ、ソート用の部品を新たに画面に表示し、renderTasks()を修正してタスクの一覧をフィルタしたり並べ替える処理を加える必要があります。

このように、要望にないことでもユーザー体験の向上で思いつくことがあれば、積極的に提案に組み込むとよいでしょう。

「読む」ことで見えてくる道筋

さて、AsIs と ToBe の整理結果をまとめて、チームリーダーに報告しましょう。

今の構造をしっかり読み解くことで、「何を変えるべきか」「どこを壊さないようにすべきか」が見えてきませんでしたか?

── 不具合の原因を見つける時
── 新しい機能を設計する時

コードを「読む力」があると、「ちゃんと理解して動けている」という手応えが得られます。

私自身も、実務の中で何度もこの力に助けられてきました。難しい不具合の原因を特定できたり、頭の中で組み立てた仮説がぴたりと当たった瞬間には、「読めたぞ!」という快感すらあります。

もちろん、読むことに慣れるには時間もかかります。でも、ひとつひとつ読み、考え、手を動かすなかで、芯となる視点が育っていきます。

読んで、理解して、構造を捉え、どんな変更が必要かを組み立てる。
こうしたプロセスは、コードを書く力にも直結します。

次章では、こうして育ててきた「読む力」が、どのようにして「書く力」へとつながるのかをお話ししていきます。