tweeeetyのぶろぐ的めも

アウトプットが少なかったダメな自分をアウトプット<br>\(^o^)/

短縮URLをcrulで叩くときに301のリダイレクトになったりする時の対処法

はじめに

リダイレクトしているようなコンテンツをcurlを使って取得したい場合に
以下のように301になってしまうので、リダイレクト先のコンテンツを取得したいときのメモです。

$ curl http://urx.mobi/GMSp
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://urx.nu/GMSp?h=urx.mobi">here</a>.</p>
<hr>
<address>Apache Server at urx.mobi Port 80</address>
</body></html>

短縮URLということ自体は直接的に関係ない話しですが、
短縮URLを使うとリダイレクトされるのでその一例ということで。

どうやるか

やりかたは簡単でオプションとして-Lを指定するだけです。

$ curl -L 

help/man

helpとmanの説明も載せておくとこんな感じでした。

# -Lのところだけ抜粋
$ curl -h
 -L, --location      Follow redirects (H)
     --location-trusted like --location and send auth to other hosts (H)

# -Lのところだけ抜粋
$ curl --man
       -L, --location
              (HTTP/HTTPS) If the server reports that the requested  page  has
              moved to a different location (indicated with a Location: header
              and a 3XX response code), this option will make  curl  redo  the
              request on the new place. If used together with -i, --include or
              -I, --head, headers from all requested pages will be shown. When
              authentication  is  used, curl only sends its credentials to the
              initial host. If a redirect takes curl to a different  host,  it
              won't  be  able to intercept the user+password. See also --loca-
              tion-trusted on how to change this. You can limit the amount  of
              redirects to follow by using the --max-redirs option.

              When  curl follows a redirect and the request is not a plain GET
              (for example POST or PUT), it will do the following request with
              a GET if the HTTP response was 301, 302, or 303. If the response
              code was any other 3xx code, curl  will  re-send  the  following
              request using the same unmodified method.

おわり

こんな単純なことでもまだまだ知らないことがありますね\(^o^)/

【JIRA】jql(JIRA Query Language)の使い方 - あいまい検索、日付指定、jql関数などのあんちょこ

はじめに

普段jiraを使っていますが、何かとfilterでのjqlはよく使います。
ただ、そのたびにこんな検索がしたいんだけどどうやるんだろう...と小一時間悩むことがあるので、そんな時のjqlメモです。  

補足

基本編とあんちょこ編に分けて書いてみました

あんちょこ編はtopicがあれば随時更新します(たぶん...

アジェンダ

  1. あいまい検索
  2. 日付の指定
  3. 必須用語/除外用語
  4. サブタスク検索
  5. WAS

1. あいまい検索

基本編でも触れましたが、
jqlは~であいまい検索ができます。

以下、例です。

普通にあいまい検索

Summary (概要)issueおよびcollectorという単語が含まれる課題をすべて検索したい

summary ~ "issue collector"
完全一致であいまい検索

issue collector という語句の完全一致が含まれる検索をしたい

summary ~ "\"issue collector\""

2. 日付の指定

まぁまぁ使うのが日付の指定です。

普通に日付指定

2017年12月12日より前に作成されたものを検索したい

created < "2017/12/12"
(+/-)nn(y|M|w|d|h|m) のオプションで日付指定

日付はyyyy/mm/ddフォーマットの他にも
(+/-)nn(y|M|w|d|h|m)のようなフォーマットで指定できます。

期限まで10日の課題

dueData =< "+14d"

3日以内に作られた課題

created >= "-3d"
jql関数で日付指定

日付用の関数も用意されています。

期限が来週いっぱいの課題

due < endOfWeek("+1")

endOfWeek()はjql関数と言って以下のようなものがあります。

関数 例題 使用例
now() 納期をすぎたもの duedate < now()
startOfDay() 3日前から作成されたもの created > startOfDay("-3d")
startOfMonth() 今月の月初以降に作成された課題 created > startOfMonth()
先月の月初以降に作成された課題 created > startOfMonth("-1")
今月の15日以降に作成された課題 created > startOfMonth("+14d")
startOfWeek() 今週の週初以降に作成された課題 created > startOfWeek()
先週の週初以降に作成された課題 created > startOfWeek("-1")
startOfYear() 今年の年初以降に作成された課題 created > startOfYear()
endOfDay() 納期が今日になっている課題 due < endOfDay()
納期が明日になっている課題 due < endOfDay("+1")
endOfMonth() 納期が今月までとなっている課題 due < endOfMonth()
納期が来月までとなっている課題を検索する 納期が来月までとなっている課題
endOfWeek() 納期が今週末までとなっている課題 due < endOfWeek()
納期が来週末までとなっている課題 due < endOfWeek("+1")
endOfYear() 納期が今年末までとなっている課題 due < endOfYear()
納期が来年の3月末までとなっている課題 due < endOfYear("+3M")

このあたりは公式にも詳しくのっています
* 詳細検索 - 機能リファレンス

3. 必須用語:+/除外用語:-

必須用語:+

必須用語というのは、単一ドキュメントのフィールド内のどこかに "+" 記号に続く用語が存在しなければならないことを意味します。
説明だとわかりにくいのでさっそく例です。

"jira" を必ず含み、"atlassian" を含む可能性のあるドキュメントを検索

+jira atlassian
除外用語:-

逆に除外用語はわかりやすいと思います。

" atlassian jira" を含むが、"japan" は含まないドキュメントを検索

"atlassian jira" -japan

このあたりは公式にも詳しくのっています
* テキストフィールドの検索の構文

4. サブタスク検索

jiraにはとあるチケットに対してsub taskという単位で子チケットを作ることができます。
サブタスク関連で検索したい時も結構ありますよね。

parent

とあるチケット(たとえばHOGE-123)に紐づくサブタスクを検索したい

parent = "HOGE-123"
issuetype

5日以内に作られたサブタスクを検索したい

created > "-5d" and issuetype = "sub-task"

5. WAS

jiraのクエリーにはwasというなかなか秀逸な演算子も存在します。
指定したフィールドの指定した値が現在存在するか、
だけでなく、過去に存在したか、というのも検索条件に含んでくれます。

これも例をみたほうが早いと思います。

プロジェクトがhogeで、アサインが現在fuga、もしくは、fugaだったことがある もので、ステータスがopenの課題

project = hoge and assignee was fuga and status = open

今のアサインがfugaでなくともhitします

おわり

なかなかイロイロな検索ができますね\(^o^)/

【JIRA】jql(JIRA Query Language)の使い方 - 検索やfilterの基本

はじめに

普段jiraを使っていますが、何かとfilterでのjqlはよく使います。
ただ、そのたびにこんな検索がしたいんだけどどうやるんだろう...と小一時間悩むことがあるので、そんな時のjqlメモです。  

補足

基本編とあんちょこ編に分けて書いてみました

あんちょこ編はtopicがあれば随時更新します(たぶん...

アジェンダ

  1. jiraの検索
  2. jql(JIRA Query Language)とは
  3. jqlとfilter
  4. jqlの基本

1. jiraの検索

jqlを使う前に、jiraの検索について軽く触れておきます。
jiraの検索は大きくわけて3つあります。

検索方法 説明
Quick search
(クイック検索)
ヘッダーにある検索ボックスに検索条件を入力して素早く検索する方法です。しかし複雑な検索はできません
Basic search
(簡易検索)
ヘッダーで、課題 > 課題を検索に移動して、検索条件を入力します。クイック検索よりも詳細な検索ができますが、高度な検索よりは柔軟に検索はできません
Advanced search
(高度な検索)
ヘッダーで、課題 > 課題を検索に移動して、検索条件を入力します。jqlというクエリで検索する事でより柔軟な検索が可能です

詳細は公式を見てみると良いです。
* [公式英語] Searching for issues * [公式日本語] 課題を検索する

公式からの引用ですが、
以下にそれぞれの簡単なイメージと説明とリンクを載せておきます。

Quick search(クイック検索)

時には、関心がある特定の課題だけ取得できればと考える場合があります。また、どの課題か覚えていないけれども、自分に割り当てられていた未処理の課題だったことだけ記憶している場合があります。このような場合はクイック検索が便利です。

f:id:tweeeety:20171110222019p:plain

Basic search(簡易検索)

ベーシック検索は、使いやすいインターフェイスを使用して、JQL の使用方法を知らなくても複雑なクエリを定義できます。

f:id:tweeeety:20171110222029p:plain

Advanced search(高度な検索)

高度な検索では JIRA クエリ言語 (JQL: JIRA Query Language) という構造的なクエリによって課題を検索することが出来ます。クイック検索やベーシック検索では定義できない検索条件を指定することができます。(例えば ORDER BY 節など) 。

f:id:tweeeety:20171110222038p:plain

2. jql(JIRA Query Language)とは

前述の通り、jqlはAdvanced search(高度な検索)を選んだ時に使えるqueryです。

特徴としては、sqlのようにselect句から書くというよりは
where句のみを書いて行くイメージです。

たとえば、jira projectが"Hoge"でチケットのstatusがopenなものは以下のように書きます

project = Hoge AND Status = Open

sqlに慣れた人であればすぐに意味がわかると思います。

3. jqlとfilter

jqlは、いろいろな検索ができますが調べたくなったら都度書いて...
なんてやりたくないですよね。

そんな時にfilterを使います。
簡単に言ってしまえば、filterはjqlに名前をつけて保存しておく機能です。

試しにfilterを作ってみるとこんな感じです。

  • jqlで検索して結果が確認できたらSave asを押します
    f:id:tweeeety:20171110222059p:plain

  • モーダルが開くのでFilter名を入れてSubmitするだけです。 f:id:tweeeety:20171110222110p:plain

これでfilterを選択すればいつでもjqlとその結果を呼び出せます。

4. jqlの基本

jqlの基本構文は以下のようになっています。

フィールド名 演算子

また、フィールド名 演算子 値 のセットを複数繋ぎたい場合はANDORを使います

フィールド名 演算子 値 AND フィールド名 演算子

以降で、フィールド名演算子キーワードについて説明します。
公式だとこのあたりです。
* 高度な検索

フィールド

jiraのフィールド名はものすっごく多いので割愛しますが、
以下のようなものがあります。

フィールド 説明
Affected version 対象バージョン affectedVersion = "3.14"
Assignee 担当者 assignee = "John Smith"
Comment コメント comment ~ "My PC is quite old"
Component コンポーネント component in (Comp1, Comp2)
Created 作成日 Created < "2010/12/12"
Created > "-1d"
Creator 作成者 Creator = "jjones"
Description 説明 description ~ "Please see screenshot"
Due 期日 due >= "2011/01/01" and due <= "2011/01/31"
Epic link エピックリンク "epic link" = Jupiter
Filter フィルター filter = 12000 and assignee = jsmith
Issue key 課題キー issueKey = ABC-123
Labels ラベル labels not in ("x") or labels is EMPTY
Parent parent = TEST-1234
Project プロジェクト project = ABC
Status ステータス status = Open
Type 課題タイプ type = Bug
issueType in (Bug,Improvement)
Updated 更新日 2週間より前
updated < "-2w"

公式にも詳しくのっています
* 高度な検索 - フィールドのリファレンス

演算子

jqlの演算子は、だいたいがsqlと同じです。
以下のようなものがあります。

演算子 説明
= 完全一致 reporter = jsmith
!= 一致しない assignee != jsmith
> 指定したフィールドの値が指定した値より大きい duedate > now()
>= 指定したフィールドの値が指定した値以上 duedate >= "2008/12/31"
< 指定したフィールドの値が指定した値より小さい votes < 4
<= 指定したフィールドの値が指定した値以下 updated <= "-4w 2d"
IN 指定したフィールドの値が指定した複数の値のいずれか reporter in (jsmith,jbrown,jjones)
NOT IN 指定したフィールドの値が指定した複数の値以外 assignee not in (Jack,Jill,John)
~ 指定したフィールドの値が、指定した値と一致する(あいまい検索) summary ~ win
!~ 指定したフィールドの値が指定した値のあいまい一致でない課題 summary !~ run
IS EMPTY または NULL とのみ一緒に使用できる fixVersion is empty
IS NOT EMPTY または NULL とのみ一緒に使用できる votes is not empty
WAS 指定したフィールドの指定した値が現在存在するか、過去に存在した課題 status WAS "In Progress"
WAS IN 指定したフィールドの指定した複数の値のいずれかが現在存在するか、過去に存在した課題 status WAS IN ("Resolved","In Progress")
WAS NOT IN 指定したフィールドの値が指定した複数の値のいずれにもなったことがない課題 status WAS NOT IN ("Resolved","In Progress")
WAS NOT 指定したフィールドが指定した値になったことがない課題 status WAS NOT "In Progress"
CHANGED 指定したフィールドの値が変更された課題 assignee CHANGED

公式にも詳しくのっています
* 高度な検索 - 演算子について

キーワード

jqlのキーワードはsqlにかなり似てるのでわかりやすいです。

キーワード 説明
AND 複数の条件の組み合わせて、検索を絞り込めます project = "New office" and status = "open"
OR 複数の条件を組み合わせて、検索範囲を広げることができます reporter = jsmith or reporter = jbrown
NOT 条件に対しての否定です not assignee = jsmith
EMPTY 指定されたフィールドに値が入力されていない課題を検索します duedate = empty
NULL 指定されたフィールドに値が入力されていない課題を検索します duedate = null
ORDER BY 検索結果の値を並べ替えるフィールドを指定します duedate = empty order by created

おわり

jiraのjqlとfilter便利ですね \(^o^)/

【git】tigでgit操作を便利にするメモ - logを可視化したりstatusやdiffも簡単に!

はじめに

gitやgithubで開発してる場合はgitコマンドもよく使いますよね。
そんな時、簡単にlogを可視化したり、簡単にいまのstatusを見たり、簡単にdiffを見たいときに使えるtigコマンドについてのメモです

アジェンダ

  1. tigとは
  2. tigのインストール
  3. tigでできること
  4. tig使ってみる
  5. よく使うtigコマンドまとめ

1. tigとは

引用ですが、こんな感じ。

Gitのためのテキストユーザインタフェースです。
Gitリポジトリ内の変更内容をVimライクな操作で高速に閲覧することができます。
参考: https://qiita.com/tyabe/items/4ce9918b6dd6971d60e4

git操作自体をvim上で行う感じ、というと雰囲気伝わるでしょうか? f:id:tweeeety:20171011231558p:plain

2. tigのインストール

mac x brewな環境ならこれだけです

$ brew install tig

3. tigでできること例

できるコトはたくさんありますが一例と、こんな感じに見えるよというのを先に記載しておきます。

一例

  • git logをいい感じに可視化
  • git statusをいい感じに可視化
  • git statusをいい感じに可視化した上でそのままステージ(git add)/アンステージできたり
  • git statusをいい感じに可視化した上でそのままdiffを見たり

4. tig使ってみる

一例をそのまんま使ってみます。

git logをいい感じに可視化

ターミナル上でgit管理化のdirに移動します。
そこで tig と打つとgit logをいい感じに表示してくれます。

f:id:tweeeety:20171011231239p:plain

ちなみに、これをmain viewと呼ぶらしいです。
この画面がすべての起点となります。

コマンド

基本的なコマンドです。

キー 挙動
j, k カーソルの上下移動
左右の矢印キー 左右にスクロール
enter サブビュー(詳細)表示
Ctrl+D/U ページ単位で上下移動
q 現在のビューを閉じる
Q tigを終了
/ 画面内検索

git statusをいい感じに可視化

statusもいい感じに可視化してくれます。
コマンドラインからはtig statusと打てばstatusが表示され、
main view を開いている状態であれば単にsと打つだけです。

f:id:tweeeety:20171011231256p:plain

それぞれの意味はgitに慣れていれば、見てわかると思いますが念のため記載しておきます。

項目

|項目|説明| |Changes to be committed:|git add したやつ(コミット対象)| |Changed but not updated:|変更があったコミット対象外)| |Untracked files:|新しく作成されたやつ(コミット対象外)|

git statusをいい感じに可視化した上でそのままステージ(git add)/アンステージできたり

また、このstatus画面では j,kで移動できたりします。
移動しながらuでそのままステージ(git add)できたりします。

statusビュー上でgit add前

f:id:tweeeety:20171011231346p:plain

statusビュー上でgit add後

f:id:tweeeety:20171011231356p:plain

コマンド
キー操作 説明
j, k カーソル移動
u ハンク単位でステージ/アンステージ

git statusをいい感じに可視化した上でそのままdiffを見たり

また、status画面でj,kで移動しながら見たいファイル上でEnterを押すとdiffも開けます。
これはdiffビューというらしいです。

mainビュー上からdiffビュー開く

f:id:tweeeety:20171011231433p:plain

statusビュー上からdiffビュー開く

f:id:tweeeety:20171011231445p:plain

コマンド

diffビュー上でも移動やらいろいろできますが、コマンドはこんな感じです。

キー操作 説明
j, k カーソル移動
ctrl-u, ctrl-d ページ単位で上下移動
u ハンク単位でステージ/アンステージ
1 行単位でステージ/アンステージ
shift-j, shift-k ファイル選択(左側のビュー)を上下移動
q diffビューを閉じる

参考サイト

おわり

tig便利!とまえまえから思ってたけど未だに意外とそのままgit statusと打ってしまいますね\(^o^)/

【windows】windows7にDisk Usageコマンド(duコマンド)を使えるようにする - ディレクトリごとの容量を調べる

はじめに

もっぱらMacユーザですがたまにwindowsも使います。

windowsディレクトリ(ファイル)ごとの容量を調べたくなりました。
linuxmacではduというコマンドを使うと簡単に調べられます。

windowsではどうやって調べるんだろうと思ったら以下の方法で行うようです。

そこで今回はduコマンド入れるを行ってみました、というメモ

アジェンダ

  1. du(Disk Usage)のインストール
  2. 使う

1. du(Disk Usage)のインストール

Windows Sysinternalsからダウンロードして使います。

  • ファイルとディスク関連のユーティリティ リンクをクリック f:id:tweeeety:20170821175308p:plain

  • Disk Usage (DU) リンクをクリック f:id:tweeeety:20170821175319p:plain

  • Du のダウンロード リンクからダウンロード f:id:tweeeety:20170821175328p:plain

2. 使う

自分は何も考えずにデスクトップに落としましたが、落としたところまで移動するかパスを指定して実行すれば使えます。
何も指定しないとこんな感じ

C:\Users\tweeeety>c:Desktop\DU\du.exe

DU v1.6 - Directory disk usage reporter
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

usage: c:Desktop\DU\du.exe [-c[t]] [-l <levels> | -n | -v] [-u] [-q] <directory>

   -c     Print output as CSV. Use -ct for tab delimiting.
          Use -nobanner to suppress banner.
   -l     Specify subdirectory depth of information (default is one level).
   -n     Do not recurse.
   -q     Quiet.
   -nobanner
          Do not display the startup banner and copyright message.
   -u     Count each instance of a hardlinked file.
   -v     Show size (in KB) of all subdirectories.

CSV output is formatted as:
Path,CurrentFileCount,CurrentFileSize,FileCount,DirectoryCount,DirectorySize,Dir
ectorySizeOnDisk
コマンド

こちらも表示されたまんまですが、以下のようにコマンドを打ちます。

c:Desktop\DU\du.exe [-c[t]] [-l | -n | -v] [-u] [-q]

Windows Sysinternals>Disk Usageに載ってますがオプションは以下です。

オプション 説明
-l 情報が必要なサブディレクトリの階層を指定します (既定ではすべてのレベルです)。
-n 再帰処理を行わないようにします。
-q バナーを出力しないようにします。
-u 同じものが複数回カウントされないようにします。
-v 中間ディレクトリの情報を表示します。
使用例

よく使うのは-l <levels>オプションでしょう。
-lを指定せずにディレクトリだけ指定すると、ディレクトリ以下のすべてのファイルがまで表示されてしまい見にくいです。

階層を指定するとその階層のディレクトリでまとめて容量を表示してくれます。

C:\Users\tweeeety>c:Desktop\DU\du.exe -l 1 C:\Users

DU v1.6 - Directory disk usage reporter
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

      22,319  c:\users\Default
      57,777  c:\users\Public
     861,279  c:\users\tweeeety
      25,060  c:\users\sakura
Files:        1920
Directories:  1677
Size:         989,631,542 bytes
Size on disk: 1,002,791,104 bytes

おわり

duは使えたもののオプションと表示が違う(-h 容量を適当な単位で表示するようなものがない)ので、少し見にくいと感じました\(^o^)/

【go】golangのエラー処理メモ - ③. pkg/errorsでのエラーハンドリング

はじめに

goをさわって数ヶ月ですが、雰囲気では書けていたものの
errorやエラーハンドリングについてはもやもやしたままだったので自分理解メモの③

関連

この記事の関連です。

アジェンダ

  1. エラーハンドリングで困る事
  2. pkg/errorsでのエラーハンドリング(errors.Wrap)
  3. pkg/errorsでのエラーハンドリング(errors.Cause)

1. エラーハンドリングで困る事

main -> packageA.method -> packageB.method のように呼び出しを行っている場合、
packageB.methodで起きたエラーをpackageA.methodで拾ってmainにさらに返したいときがあります。

packageA.methodでのハンドリングによっては、
mainでなんのエラーだったかわかりにくくなるときがあります。

サンプル

ここでは main -> fuga.method -> hoge.method のように呼び出しているとします。

hoge.methodが返すエラーをfuga.method
ハンドリングしてさらにエラーを返します。

mainではerrrorかどうかをハンドリングします。

コード
package hoge

import "fmt"

type HogeSomethingError struct{}

func (f *HogeSomethingError) Error() string {
  return fmt.Sprintf("this is HogeSomethingError")
}

type HogeAnythingError struct{}

func (f *HogeAnythingError) Error() string {
  return fmt.Sprintf("this is HogeAnythingError")
}

func DoSomething() error {
  return &HogeSomethingError{}
}

func DoAnything() error {
  return &HogeAnythingError{}
}

func DoExciting(b bool) error {
  if b {
    return DoSomething()
  } else {
    return DoAnything()
  }
  return nil
}
package fuga

import (
  "errors"
  "go-error-handling_pkg-errors/package/hoge"
)

func DoExciting(b bool) error {
  err := hoge.DoExciting(b)

  if err != nil {
    switch err.(type) {

    case *hoge.HogeSomethingError:
      return errors.New("error1 at fuga")

    case *hoge.HogeAnythingError:
      return errors.New("error2 at fuga")

    }
  }

  return nil
}
package main

import (
  "fmt"
  "os"

  "go-error-handling_pkg-errors/package/fuga"
)

func main() {
  if err := fuga.DoExciting(false); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample01/main.go 
error2 at fuga
exit status 1
困ること

mainでは、error2 at fuga と出力され、
エラーがあったことはわかるものの、hogeでエラーだったことがわかりません。

2. pkg/errorsでのエラーハンドリング(errors.Wrap)

そんな場合に pkg/errors でエラーハンドリングを行ってみます。

インストール

使う前にインストールしておきます。

pkg/errrosgo getなら以下のようにしてインストールできます。

go get -v github.com/pkg/errors

自分はglide環境なので、glide.yamlを以下のようにしてglide installしました。

package: go-error-handling_pkg-errors
import:
  - package: github.com/pkg/errors

サンプル

さきほどの fuga.methodpiyo.methodに置き換えて、
main -> piyo.method -> hoge.method と呼び出すようにします。

また、piyo.methodでは`pkg/errorsを使ってエラーハンドリングするようにしてみます。

コード
上記と同じです
package piyo

import (
  "go-error-handling_pkg-errors/package/hoge"

  "github.com/pkg/errors"
)

func DoExciting(b bool) error {
  err := hoge.DoExciting(b)

  if err != nil {
    switch err.(type) {

    case *hoge.HogeSomethingError:
      return errors.Wrap(err, "error1 at piyo")

    case *hoge.HogeAnythingError:
      return errors.Wrap(err, "error2 at piyo")

    }
  }

  return nil
}
package main

import (
  "fmt"
  "os"

  "go-error-handling_pkg-errors/package/piyo"
)

func main() {
  if err := piyo.DoExciting(false); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample02/main.go 
error2 at piyo: this is HogeAnythingError
exit status 1
結果

error2 at piyoに続いて、大元の: this is HogeAnythingErrorも表示されました。

3. pkg/errorsでのエラーハンドリング(errors.Cause)

errors.Causeというのもあり、
これを使うと上記の例でいうとmainで原因となるエラーの型ハンドリングが可能となります。

mainだけ変更した例をのせておきます。

コード
package main

import (
  "fmt"
  "os"

  "github.com/pkg/errors"

  "go-error-handling_pkg-errors/package/hoge"
  "go-error-handling_pkg-errors/package/piyo"
)

func main() {
  if err := piyo.DoExciting(false); err != nil {
    switch errors.Cause(err).(type) {
    case *hoge.HogeSomethingError:
      fmt.Fprintln(os.Stderr, "case1 at main:", err)
    case *hoge.HogeAnythingError:
      fmt.Fprintln(os.Stderr, "case1 at main:", err)
    }
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample03/main.go 
case1 at main: error2 at piyo: this is HogeAnythingError
main success

おわり

こちらの参考でも言われてますが、
たしかにあまり深くWarpしまくるのも、という気もするので結局悩ましいループになりそう\(^o^)/

参考

【go】golangのエラー処理メモ - ②. 例外はないがエラーハンドリングはできるよ(インスタンスや型でハンドリング)

はじめに

goをさわって数ヶ月ですが、雰囲気では書けていたものの
errorやエラーハンドリングについてはもやもやしたままだったので自分理解メモの②

関連

この記事の関連です。

アジェンダ

  1. goのエラーについて
  2. 一番簡単なエラーハンドリング
  3. エラーハンドリングのパターン
  4. パターン1: errorの文字列で
  5. パターン2: errors.Newのインスタンス
  6. パターン3: カスタムエラーのインスタンス
  7. パターン4: カスタムエラーの型で

1. goのエラーについて

goのエラーについては以下に記載してみました。
【go】golangのエラー処理メモ - ①. errorとError型とカスタムErrorと

あえて項目にするまでもなかったけど前提として載せておきます

2. 一番簡単なエラーハンドリング

goは例外というモノがなく、
基本的には関数を呼び出した結果がエラーな場合はその場で処理します。

簡単なエラーハンドリング

その場で処理しちゃう例の一番簡単なものです。

サンプル

サンプルコード

package main

import (
  "errors"
  "fmt"
  "os"
)

func doSomething() error {
  return errors.New("doSomething is error.")
}

func main() {

  err := doSomething()

  // nilと比較してerrorオブジェクトが返ってたらエラーと見なす
  if err != nil {
    fmt.Println("main is failed")
    os.Exit(1)
  }

  fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample01/main.go 
main is failed
exit status 1

エラーを無視する

また、エラーでも問題ない場合は_や単に何も受け取らないことで無視する事もできます。

サンプル

サンプルコード

package main

import (
  "errors"
  "fmt"
)

func doSomething() error {
  return errors.New("doSomething is error.")
}

func doHoge() {
  err := doSomething()

  // nilと比較してerrorオブジェクトが返ってたらエラーと見なす
  if err != nil {
    fmt.Println("doHoge is failed")
    return
  }

  // 処理を続ける
  fmt.Println("doHoge is success")
}

func doFuga() {

  // _ でerrorを無視する
  _ = doSomething()

  // 処理を続ける
  fmt.Println("doFuga is success")
}

func doPiyo() {
  // 単に受け取らないことも
  doSomething()

  // 処理を続ける
  fmt.Println("doPiyo is success")
}

func main() {

  doHoge()

  doFuga()

  doPiyo()

  fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample02/main.go 
doHoge is failed
doFuga is success
doPiyo is success
main is success

3. エラーハンドリングのパターン

上記のような簡単なエラーハンドリングでも事足りる事はあります。

しかし、
実際は下位のmethodで発生したエラーをハンドリングしては上位に返して…
を繰り返すと困る事がでてきます。

自分は以下のような事が困りました。

  • 最上位でハンドリングで出力したものの根本原因がどこかわからない
  • エラーによって分岐したいがどのようにするのがベストプラクティスかわからない

エラーハンドリングのパーターンとしては以下のサイトが非常に参考になります。

参考サイトを参考に、
エラーハンドリングは以下のようなパターンで行う事ができます。

エラーハンドリングのパターン
  • パターン1: errorの文字列で
  • パターン2: errors.Newのインスタンス
  • パターン3: カスタムエラーのインスタンス
  • パターン4: カスタムエラーの型で

バッドノウハウなパターンも含まれてますが、
さっと書く使い捨てのscriptだったりではバッドノウハウパターンでも無いよりは良いかな、という印象です。

4. パターン1: errorの文字列で

errors.Newなりfmt.Errorfの文字列を受け取り側で比較するというものです。

単純ですがバッドノウハウとされているパターンです。

サンプル

サンプルコード

package main

import (
  "errors"
  "fmt"
  "os"
)

const (
  ERROR_MSG_01 = "doSomething is error. b is true"
  ERROR_MSG_02 = "doSomething is error. b is false"
)

func doSomething(b bool) error {

  if b {
    return errors.New(ERROR_MSG_01)

  } else {
    return errors.New(ERROR_MSG_02)
  }

  return nil
}

func main() {

  err := doSomething(true)

  if fmt.Sprintf("%s", err) == ERROR_MSG_01 {
    fmt.Println("ERROR: ERROR_MSG_01に応じた処理を行う")
    os.Exit(1)

  } else if fmt.Sprintf("%s", err) == ERROR_MSG_02 {
    fmt.Println("ERROR: ERROR_MSG_02に応じた処理を行う")
    os.Exit(1)
  }
  fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample03/main.go 
ERROR: ERROR_MSG_01に応じた処理を行う
exit status 1
困る点

ハンドリング後、さらに呼び出し元に返そうとするととたんに困ります…

  // fmt.Errorfでさらに呼び出し元に返すとハンドリングできなく...
  if fmt.Sprintf("%s", err) == ERROR_MSG_01 {
    return fmt.Errorf("ERROR: err=%+v", err)
  }

5. パターン2: errors.Newのインスタンス

errors.Newのインスタンスで比較するパターンです。

osパッケージなんかでも使われてたりします
https://golang.org/src/os/error.go#L11

サンプル

サンプルコード

package main

import (
  "errors"
  "fmt"
  "os"
)

const (
  ERROR_MSG_01 = "doSomething is error. b is true"
  ERROR_MSG_02 = "doSomething is error. b is false"
)

var (
  ERROR_01 = errors.New(ERROR_MSG_01)
  ERROR_02 = errors.New(ERROR_MSG_02)
)

func doSomething(b bool) error {

  if b {
    return ERROR_01

  } else {
    return ERROR_02
  }

  return nil
}

func main() {

  err := doSomething(false)

  if err == ERROR_01 {
    fmt.Println("ERROR: インスタンスERROR_01に応じた処理を行う")
    os.Exit(1)

  } else if err == ERROR_02 {
    fmt.Println("ERROR: インスタンスERROR_02に応じた処理を行う")
    os.Exit(1)
  }
  fmt.Println("main is success")
}

コード自体はだいぶマシになった気はしてきます

出力
$ go run src/go-error-handling/sample04/main.go 
ERROR: インスタンスERROR_02に応じた処理を行う
exit status 1
困る点

パターン1と同様ですがハンドリングの結果、上位にエラーを返すときに困ります。

そのまま返すとどこでエラーだったのかわかりずらくなり、
fmt.Errorfなどで新規にインスタンスを作ってしまうと別物になってしまいます。

  // そのまま返し続けると結局どこのエラーだったのか...という感じに
  if err == ERROR_01 {
    return ERROR_01
  }
  // fmt.Errorfをかますと、違うインスタンスになってしまう
  if err == ERROR_01 {
    return fmt.Errorf("ERROR: err=%+v", ERROR_01)
  } 

6. パターン3: カスタムエラーのインスタンス

【go】golangのエラー処理メモ - ①. errorとError型とカスタムErrorと
でも触れたカスタムエラーを定義してそのインスタンスでハンドリングするパターンです。
1つのカスタムエラーを使い回してますが、カスタムエラー自体を複数用意しても良いです。

サンプル

サンプルコード

package main

import "fmt"

const (
  ERROR_MSG_01 = "doSomething is error. b is true"
  ERROR_MSG_02 = "doSomething is error. b is false"
)

type MyError struct {
  Msg  string
  Code int
}

func (err *MyError) Error() string {
  return fmt.Sprintf("%s, %d", err.Msg, err.Code)
}

var (
  MyError_01 = &MyError{Msg: "MyError_001 is occur", Code: 30001}
  MyError_02 = &MyError{Msg: "MyError_002 is occur", Code: 30002}
)

func doSomething(b bool) error {

  if b {
    return MyError_01

  } else {
    return MyError_02
  }

  return nil
}

func main() {

  err := doSomething(false)

  switch err {
  case MyError_01:
    fmt.Println("ERROR: インスタンスMyError_01に応じた処理")
    fmt.Printf("ERROR: %+v\n", err)

  case MyError_02:
    fmt.Println("ERROR: インスタンスMyError_02に応じた処理")
    fmt.Printf("ERROR: %+v\n", err)

    // ちなみにdoSomethingの返り値は
    // errorインターフェースであってMyError_01ではない。
    // ココに以下のようなコードは書けない
    //fmt.Printf("ERROR: Msg=%s, Code=%d", err.Msg, err.Code)
  }
  fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample05/main.go 
ERROR: インスタンスMyError_02に応じた処理
ERROR: MyError_002 is occur, 30002
main is success
困る点

パターン2に同じ

7. パターン4: カスタムエラーの型で

カスタムエラーの型でハンドリングするパターンです。

err.(type)という記述をしますが、
これはerrorがinterface型のために行えるらしいです。(知らなかった)

また、これをConversion構文というらしいです。
https://golang.org/ref/spec#Conversions

サンプル

サンプルコード

package main

import "fmt"

type MyError_01 struct {
  Code int
}

func (err *MyError_01) Error() string {
  return fmt.Sprintf("this is MyError_01")
}

type MyError_02 struct {
  Code int
}

func (err *MyError_02) Error() string {
  return fmt.Sprintf("this is MyError_02")
}

func doSomething(b bool) error {

  if b {
    return &MyError_01{Code: 30001}

  } else {
    return &MyError_02{Code: 30002}
  }

  return nil
}

func main() {

  err := doSomething(false)

  switch e := err.(type) {
  case *MyError_01:
    fmt.Println("ERROR: MyError_01の型に応じた処理")
    fmt.Printf("ERROR: err=%+v, e=%+v, code=%d\n", err, e, e.Code)

  case *MyError_02:
    //fmt.Printf("ERROR: MyError_02のエラー, err=%+v, code=%d\n", e, e.Code)
    fmt.Println("ERROR: MyError_02の型に応じた処理")
    fmt.Printf("ERROR: err=%+v, e=%+v, code=%d\n", err, e, e.Code)
  }

  fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample06/main.go 
ERROR: MyError_02の型に応じた処理
ERROR: err=this is MyError_02, e=this is MyError_02, code=30002
main is success
困る点

これまでの中では一番すっきりした感があります。

しかし、参考サイトまんまの受け売りですが、
このままerrorを上に上に伝搬させていくと
上位ロジックが全てのerrorを把握している必要がある、という感じ。

ではどうするのか

pkg/errorsを使うと良いらしい
が、長くなったのでまた次の記事にします。

これもまた参考サイトが非常に参考になりました!

おわり

errorについても奥が深い!\(^o^)/