osio_sioの日記

自分用メモ

GOのエラーハンドリングについて

はじめに

インターンを通して、GO(echo)を用いたAPIの実装を行ったのですが、その中でエラーハンドリングの基礎について勉強になったのでメモとして残しておきます✍️

前提の考え方

まず、前提として、エラーログを出すときは、 "(自分が追加したメッセージ) + 元のエラー" といった風に、元のエラーは必ず出したほうがいいです。これは、根本的原因が元のエラーに入っているためです。
(ちなみに、エラーに何が入っているか確認する方法としては、
・fmt.Println などで標準出力に出す
・Logger を使う
・レスポンスに含める
IDE のデバッガ機能などで変数の中身を見る
などがあります)

また、レスポンスとログに出すエラーとしては、以下のような違いがあります。
レスポンスに出すエラー:クライアント側(フロントエンドなど)の知りたい情報を返す
ログに出すエラー : 自分側(バックエンド)の知りたい情報を返す

クライアント側の知りたい情報は、例えばhttp status codeなどが挙げられます。
(ステータスコード一覧)

具体的なコード例

以上を踏まえた実際のコード例はこんな感じになりました。

if err != nil {
    c.Logger().Errorf("ログに出したいメッセージ: %v", err)
    return echo.NewHTTPError(http.StatusBadRequest, "クライアントに表示したいエラーメッセージ")
}

ref:https://echo.labstack.com/docs/error-handling ref:https://github.com/labstack/echo/blob/v4.11.4/echo.go#L887-L893

fmt.Errorfを使う場合

また、fmt.Errorfを使う場合もあります。これは、新しいエラーをフォーマットして生成するために使用されます。エラーメッセージに追加のコンテキスト情報を含めたい場合や、既存のエラーに関する情報をラップしてさらに上のレイヤーに伝播させたい場合に便利です。エラーをラップすると、errors.Isやerrors.As関数を使って後でそのエラーを検査することができます。
例えば、以下のように使うことができます。

func openFile(filename string) error {
    _, err := os.Open(filename)
    if err != nil {
        // エラーをラップして追加の情報を含めて返す
        return fmt.Errorf("ファイル '%s' のオープンに失敗しました: %w", filename, err)
    }
    return nil
}

func main() {
    err := openFile("nonexistent_file.txt")
    if err != nil {
        // os.ErrNotExistエラーかどうかをチェック
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("ファイルが存在しません。:", err)
            // ここにファイルが存在しない場合の特定の処理を書く
        } else {
            fmt.Println("予期せぬエラーが発生しました。:", err)
        }
    }
}

%wは、fmt.Errorf関数内でのみ特別な意味を持ち、エラーをラップして、エラーの詳細を保持しながら新しいエラーを生成することができます。

また、以上の内容から分かるように、errorの比較はif err == fs.ErrExist とするのではなく、errors.Isを使ってif errors.Is(err, fs.ErrExist)とするほうが、エラーがラップされていても成功するためよさそうです。 ref: https://pkg.go.dev/errors

leetcodeのListNodeについてメモ

始めに

つい最近leetcodeを始めたものの、以下のような問題が出てきてListNodeってなんぞ?となって詰まったのでメモ。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        
    }
};

ListNodeとは何か

ちょっと調べたところ、「単方向連結リスト」のことらしい。 具体的に書いておくと、データを格納する「要素」と「ポインタ」を組にした「セル」と呼ばれる単位で,データの順序を管理する連結リスト構造。コードでいうint valが要素でListNode *nextが次のListNodeオブジェクトへのポインタ。 ref:http://www.ced.is.utsunomiya-u.ac.jp/lecture/2022/prog/p2/kadai2/3_list_1.php

ListNode() : val(0), next(nullptr) {}は、デフォルトコンストラクタ。valを0で初期化し、nextをnullptr(次のノードがないことを意味する)で初期化。

ListNode(int x) : val(x), next(nullptr) {}は、引数として整数xを取るコンストラクタ。valをxで初期化し、nextをnullptrで初期化。

ListNode(int x, ListNode *next) : val(x), next(next) {}は、引数として整数xとListNodeへのポインタnextを取るコンストラクタ。valをxで初期化し、nextを引数で与えられたnextで初期化。

操作方法

  1. ノードの追加 リストの終端に新しいノードを追加する
   ListNode* newNode = new ListNode(5); // 値が5の新しいノードを作成
   if (head == nullptr) {
       head = newNode; // リストが空の場合、headを新しいノードに設定
   } else {
       ListNode* current = head;
       while (current->next != nullptr) {
           current = current->next; // リストの最後まで移動
       }
       current->next = newNode; // 最後のノードのnextを新しいノードに設定
   }
  1. ノードの削除 リストからノードを削除するには、削除するノードの前のノードのnextを、削除するノードの次の ノードに設定
   ListNode* current = head;
   ListNode* previous = nullptr; //削除したいノードの前のノードを見つけたい
   while (current != nullptr && current->val != value) { // valueは削除したい値
       previous = current;
       current = current->next;
   }
   if (current != nullptr) { // 削除するノードが見つかった場合
       if (previous != nullptr) {
           previous->next = current->next;
       } else {
        head = current->next; // ヘッドを削除する場合
       }
       delete current; // ノードのメモリを解放
   }

問題(今後追記)

Add Two Numbers : https://leetcode.com/problems/add-two-numbers/?envType=list&envId=xi4ci4ig