PowerShellでToDoリストを改善する – Part 3: エラーハンドリングの導入

※本サイトはプロモーションが含まれています。

プログラムが何かの原因でうまく動作しなかったとき、その原因をユーザーや開発者に知らせる方法が必要です。この役割を果たすのが「エラーハンドリング」です。エラーハンドリングを適切に実装することで、プログラムが突然終了したり、不適切な結果を出力するのを防ぐことができます。さらに、問題の原因や解決策を示す明確なエラーメッセージを提供することで、ユーザーの混乱やフラストレーションを軽減することもできます。

上司
上司

最近ToDoリストがエラーを出して動かなくなるんだけど…

猫道
猫道

必要なファイル配置してますか?

上司
上司

そんなこと言われても、エラーメッセージがよくわからないよ!

猫道
猫道

自分で読めよ…

PowerShellでのエラーハンドリングの基礎

PowerShellにおけるエラーハンドリングは、予期しないエラーや予測できるエラーを適切に取り扱うための重要な技術です。

基本的には、エラーが発生する可能性のあるコードブロックをTryブロックで囲み、エラーが発生した際の処理をCatchブロックで記述します。さらに、エラーの有無に関係なく最後に実行する処理はFinallyブロック内に記述します。また、予測できるエラーについては個別に取り扱います。

予測できるエラーの取り扱い

予測できるエラーは、特定の条件を満たさない場合にエラーとして扱うことができます。例として、ファイルが存在しない場合にエラーとする場合のコードを考えてみましょう。

if (-not (Test-Path "C:\path\to\file.txt")) {
    throw "ファイルが存在しません!"
}

このコードは、指定されたパスにファイルが存在しない場合、エラーメッセージをスローします。

Try、Catch、Finallyの使用

エラーの発生を想定するブロックをTry内に記述し、エラーが発生した場合の処理をCatch内で記述します。最後にFinallyブロックで、エラーの有無にかかわらず実行する処理を記述します。

Try {
    # エラーが発生する可能性のある処理
    $content = Get-Content "C:\path\to\file.txt"
}
Catch {
    # エラーが発生した際の処理
    Write-Host "エラーが発生しました: $($_.Exception.Message)"
}
Finally {
    # 最終的な処理
    Write-Host "処理が完了しました"
}

このコードでは、Get-Contentコマンドレットで指定されたパスのファイルを読み込む際にエラーが発生する可能性を考慮しています。エラーが発生した場合、Catchブロックでエラーメッセージを表示し、最後にFinallyブロックで”処理が完了しました”というメッセージを表示します。

Finallyは任意で、Try~Catch句でも使用できます。

ToDoリストにエラーハンドリングを導入するステップ

ToDoリストの管理には、エラーが発生する様々なシチュエーションが考えられます。それを適切に処理するための手順を以下に示します。

ステップ1: 入力値の検証

ユーザーからの入力値が予期しないものであることがエラーの大きな原因です。具体的には、以下のような場面でのエラーハンドリングが考えられます。

  1. タスクの名前が空であるか、過度に長い場合
  2. 削除するタスクの番号が範囲外である場合

例えば、AddTask関数でタスクの名前が空でないかを確認する処理を追加することができます。

function AddTask {
    $task = Read-Host "追加したいタスクを入力してください"
    #タスク名が空、または、100文字以上
    if (-not $task or $task.Length -gt 100) {
        Write-Host "タスク名が不正です。タスク名を入力してください。"
        #処理を抜ける
        return
    }
    $script:tasks += $task
    Write-Host "$task がToDoリストに追加されました。"
}
猫道
猫道

2.については既に取り込んでいるよ。

ステップ2: ファイル操作時のエラー処理

ファイルの読み書きはエラーが発生しやすい操作の一つです。以下の点を注意してハンドリングします。

  1. ファイルが存在しない場合
  2. ファイルの読み書きに失敗した場合

上記1.を考慮し、ファイルを読み込む前にファイルの存在チェック処理を追加します。
処理を止めることも出来ますが、ここでは新規ファイルを作成し処理を続行する作りとします。
上記2.についてはステップ3でまとめて取り扱うことにします。

# ファイル存在チェック
if (-not (Test-Path $tasksFile)) {
    #新規ファイル作成
    #$nullについて 作成結果を画面に出力しないようにする
    New-Item $tasksFile -ErrorAction Stop >$null
}

ステップ3: その他の一般的なエラーの処理

一般的なエラーの処理として、未知のエラーや特定できないエラーが発生した場合のハンドリングが考えられます。これには、全体的なエラーハンドリングを実装する方法が考えられます。例として、メインのメニュー部分にTry-Catchを適用することで、どの操作を選択してもエラーが発生した場合の処理を一元的に行うことができます。

Try {
    #処理全般
} Catch {
    #エラーメッセージ出力
    Write-Host "予期せぬエラーが発生しました: $($_.Exception.Message)"
}

これにより、エラーが発生した場合には該当するエラーメッセージをユーザーに表示し、プログラムの安定性を向上させることができます。

ステップ4: その他の考慮

ログ出力の追加

開始/終了のメッセージやエラーメッセージをログ出力することで、ログを送付してもらうだけで処理がどこまで動いたか、どのようなエラーが出力されたか等、ある程度の判断が出来るようになります。
ここでは予期せぬエラーが発生した場合のログ出力処理を追加します。

ToDoList.ps1
#ログファイル設定追加
    # ログファイル設定
    $logFile = "$commonPath\log\log.txt"
    

#Catch処理にログ出力追加
} Catch {
    #エラーメッセージ出力
    Write-Host "予期せぬエラーが発生しました: $($_.Exception.Message)"
    
    #ログファイル出力(-Appendは追記モード。echoは表示コマンド)
    echo "予期せぬエラーが発生しました: $($_.Exception.Message)" | Out-File -Append $logFile
}

ログの出力先フォルダを追加します。

待機処理の追加

エラー発生時に画面が一瞬で閉じられてしまうため、batファイルに待機処理を追加します。

ToDoList.bat
@echo off
powershell.exe -ExecutionPolicy RemoteSigned "%~dp0bin\ToDoList.ps1"

pause

エラーハンドリング後のToDoリストの動作確認

エラーハンドリングを導入した後のToDoリストの動作を確認することで、ユーザーが遭遇する様々なエラーシチュエーションでのアプリケーションの挙動を把握することができます。

確認シナリオ1: タスク名の入力

操作: タスク追加を選択し、空のタスク名を入力

期待する動作: “タスク名が不正です。タスク名を入力してください。”というメッセージが表示され、タスクは追加されない。

確認シナリオ2: ファイルの存在チェック

操作: タスクの読み込みを選択する前に、タスクデータを保存しているファイルを削除

期待する動作: 新規のファイルが作成されて処理が続行される。

確認シナリオ3: 不明なエラー

操作: アプリケーション内で未予測のエラーを意図的に発生させる (例: 「data」フォルダのファイル名を変更する等)

期待する動作: “予期せぬエラーが発生しました: [エラーメッセージ]”というメッセージが表示される。

上司
上司

これなら私にもなんとなく分かるよ。

猫道
猫道

よかったです!解決出来ない時はログファイルを送ってくださいね。

ソースコード(完成版)

完成したソースコードを記載します。

ToDoList.bat
@echo off
powershell.exe -ExecutionPolicy RemoteSigned "%~dp0bin\ToDoList.ps1"

pause
ToDoList.ps1
Try {
    
    # 共通のパスの設定
    $commonPath = "$PSScriptRoot\.."
    
    # tasksファイル設定
    $tasksFile = "$commonPath\data\tasks.txt"
    
    # ログファイル設定
    $logFile = "$commonPath\log\log.txt"
    
    # タスクの配列を宣言
    $tasks = @()
    
    # ファイル存在チェック
    if (-not (Test-Path $tasksFile)) {
        #新規ファイル作成
        New-Item $tasksFile -ErrorAction Stop >$null
    }
    
    # ファイル読み込み
    $tasks += Get-Content $tasksFile
    Write-Host "タスクがファイルから読み込まれました。"
    
    function AddTask {
        $task = Read-Host "追加したいタスクを入力してください"
        #タスク名が空、または、100文字以上
        if (-not $task -or $task.Length -gt 100) {
            Write-Host "タスク名が不正です。タスク名を入力してください。"
            #処理を抜ける
            return
        }
        $script:tasks += $task
        Write-Host "$task がToDoリストに追加されました。"
    }
    
    # タスクの表示機能
    function ShowTasks {
        Write-Host "現在のタスク一覧:"
        for ($i=0; $i -lt $script:tasks.Length; $i++) {
            Write-Host ("[" + ($i + 1) + "] " + $script:tasks[$i])
        }
    }
    
    # タスクの削除機能
    function RemoveTask {
        # 現在のタスク一覧を表示
        ShowTasks
    
        # ユーザーに削除したいタスクの番号を入力させる
        $index = Read-Host "削除したいタスクの番号を入力してください"
    
        # 入力された番号が有効な範囲内にあるかを確認
        if ($index -ge 1 -and $index -le $script:tasks.Length) {
            # 選択された番号のタスクを取得
            $removedTask = $script:tasks[$index-1]
    
            # タスクが1つだけの場合、タスクの配列を空にする
            if ($script:tasks.Length -eq 1) {
                $script:tasks = @()
            } 
            # 最初のタスクを削除する場合
            elseif ($index -eq 1) {
                $script:tasks = $script:tasks[1..($script:tasks.Length-1)]
            } 
            # 最後のタスクを削除する場合
            elseif ($index -eq $script:tasks.Length) {
                $script:tasks = $script:tasks[0..($index-2)]
            } 
            # 最初や最後以外のタスクを削除する場合
            else {
                $script:tasks = $script:tasks[0..($index-2)] + $script:tasks[$index..($script:tasks.Length-1)]
            }
    
            # 削除されたタスクの情報を表示
            Write-Host "$removedTask がToDoリストから削除されました。"
        } else {
            # 無効な番号が入力された場合のメッセージを表示
            Write-Host "無効な番号です。再度選択してください。"
        }
    }
    
    # タスクの保存機能
    function SaveTasks {
        $script:tasks | Out-File $tasksFile
        Write-Host "タスクがファイルに保存されました。"
    }
    
    # メニュー表示
    do {
        $choice = Read-Host "操作を選択してください (1: タスク追加, 2: タスク表示, 3: タスク削除, 4: タスク保存, 5: 終了)"
        switch ($choice) {
            '1' { AddTask }
            '2' { ShowTasks }
            '3' { RemoveTask }
            '4' { SaveTasks }
            '5' { Write-Host "プログラムを終了します。"; break }
            default { Write-Host "無効な選択です。再度選択してください。" }
        }
    } while ($choice -ne '5')
}Catch{

    #エラーメッセージ出力
    Write-Host "予期せぬエラーが発生しました: $($_.Exception.Message)"
    
    #ログファイル出力
    echo "予期せぬエラーが発生しました: $($_.Exception.Message)" | Out-File -Append $logFile
}

まとめ

今回はToDoリストのアプリケーションにエラーハンドリングを導入するプロセスについて学びました。エラーハンドリングは、ユーザーがアプリケーションをスムーズに使用できるようにするための必須の要素です。ユーザーの入力や外部ファイルの取り扱いなど、エラーが発生しうる箇所に適切なエラーメッセージや処理を追加することで、アプリケーションの信頼性と使いやすさが大幅に向上します。

今回の要点を再確認しましょう:

  1. ユーザー入力のエラーハンドリング: タスクの名前や期限などの入力項目を正しく検証。
  2. ファイル操作時のエラー処理: ファイルの存在チェックや読み書きエラーのハンドリング。
  3. 一般的なエラーの処理: 予期せぬエラーに対する全体的なエラーハンドリング。

お疲れ様でした。これにてToDoリストの完成を迎えることができました!
次のステップとして、今回得た知識を元にデータ管理ツールの作成手順を用意しています。次回も、新たな知識やスキルを一緒に学びながら、さらなる成長を目指しましょう!

🔍 探求を続ける

🔹 次のステップへ進む
🔗 「PowerShellでのデータ管理ツールの設計と実装の全手順」を読む

🔹 全体像の確認(ToDoリスト作成)
🔗 「PowerShellを活用したToDoリスト作成の全手順」を見る

ToDoリスト作成
シェアする
猫道をフォローする
この記事を書いた人
猫道

異業種からの転職を経て、システムエンジニアとして7年以上働いてきました。PowerShellを使いながら、プログラミングの魅力を共有したいと思っています。

猫道をフォローする

コメント

タイトルとURLをコピーしました