yn2011's blog

技術メモ

bashコマンドは読取権限のあるファイルなら処理できる

環境

Permission denied

  • $ touch hoge.sh && ./hoge.shするとbash: ./hoge.sh: Permission deniedになる
    • 実行権限がない
  • しかし$ touch hoge.sh && bash hoge.shは正常終了するのはなぜか?

読取権限

  • $ chmod u-r hoge.shして所有者の読取権限を剥奪するとbash hoge.shbash: ./hoge.sh: Permission deniedとなる
  • bashコマンドはファイルから読み取った内容をbashコマンドが実行しているのに対して、.コマンドはファイルから読み取った内容を実行する際にOSの実行権限が必要になっている模様(推論、あまり詳しくないので違っていたら教えてください)

httptest.Serverとhttptest.ResponseRecorderの使い分け

環境

go1.12.4 darwin/amd64

httptest.Serverとhttptest.ResponseRecorder

httptest.Server

  • httptest.Serverは外部のHTTPサーバと通信を行うコードをテストするために利用する(外部APIの挙動のエミュレート)
    • クライアント-サーバモデルで言うと、テストしたい対象がクライアントの場合に相当する模様

httptestパッケージより引用

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httptest"
)

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

    res, err := http.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", greeting)
}

httptest.NewRecorder

  • httptest.NewRecorderは純粋にHTTPリクエストに対するハンドラーの動作をテストするために利用する
    • 例えばHello worldを返すだけのWebサーバのコードを書いた場合のテストコードはhttptest.NewRecorderで十分

httptestパッケージより引用

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
)

func main() {
    handler := func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "<html><body>Hello World!</body></html>")
    }

    req := httptest.NewRequest("GET", "http://example.com/foo", nil)
    w := httptest.NewRecorder()
    handler(w, req)

    resp := w.Result()
    body, _ := ioutil.ReadAll(resp.Body)

    fmt.Println(resp.StatusCode)
    fmt.Println(resp.Header.Get("Content-Type"))
    fmt.Println(string(body))

}

関連:NewRecorderを利用する場合にserveHTTPを使用する必要があるのか

  • http.HandlerFunc(articlesHandler).ServeHTTP(rr, req)してレスポンスを検証している例もある
  • マルチプレクサ経由でhandlerを呼び出している(HandleFuncはマルチプレクサ(ServeMux)を返し、ServeHTTPはマルチプレクサに登録されているパターンにマッチするハンドラを呼び出す)
    • 結局テストコード中でハンドラを登録しているなら初めからServerを使うか、ハンドラの処理だけにフォーカスするかした方が良いのではないか?(Goのhttpパッケージをテストしたいわけではない)

参考

draw.ioで両端が矢印の線を引く方法

www.draw.io

AWSの構成図をdraw.ioで描いていて、両端が矢印の線を引く方法が分からなかった。もう諦めてCacooを使おうかと思っていたところ解決策が見つかった。

How to create a bidirectional arrow? : draw.io Helpdesk

ちなみに上記で操作しているサイドバーはcmd+shift+pで表示できる(Macの場合)

VSCodeでGoのデバッグ

今更感はあるがVSCodeでGoのソースコードデバッグしてみたので記録。

環境

delveインストール

github.com

VSCodeにテスト対象の$GOPATHを認識させる

  • 方法は複数あるが、VSCodeのworkspaceディレクトリを自動的に$GOPATHとして認識してくれるようにするのが1番手軽そう

launch.jsonを作る

  • テスト対象のプロジェクトをVSCodeで開き、[構成の追加]をクリック(画像は既に構成追加済み)

f:id:pokuwagata:20190615215335p:plain

  • launch.jsonが作成される(作成済みの場合は追記される)
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${fileDirname}",
      "env": {},
      "args": []
    }
  ]
}

デバッグする

  • breakpointを置く
  • テストコードの[debug test]をクリックする。テストメソッド単位で実行できる。

f:id:pokuwagata:20190615220016p:plain

  • breakpointの位置で処理が止まる。テストメソッドの中でも止められるし、テストメソッドから呼ばれた先のメソッドの中でも止められる。
  • これでテストが通らなくなった場合の原因調査がスムーズに

f:id:pokuwagata:20190615220221p:plain

まとめ

  • VSCodeソースコードのdebugを行う際は、launch.json等の設定ファイルへの習熟が必要な印象があったが、Goの場合はあまり考えることがない
  • delveは必要だったが、それ以外はVSCodeとGo本体というシンプルな構成でデバッグ可能

Goやっていきましょう

型アサーションでインターフェイス型から本来の型を取り出す際の演算子*

環境

インターフェイス型の型アサーション

Goでは、以下のようにインターフェイスエイリアスであるStatement型の変数から実際の型であるExpressionStatementを型アサーション.(T)によって取り出すことができる。

package main

import "fmt"

type Statement interface {
    // [...]
    statementNode()
}

type ExpressionStatement struct {
    // [...]
    hoge string
}

func (es *ExpressionStatement) statementNode() {
    // [...]
}

func getExpressionStatement() Statement {
    return &ExpressionStatement{hoge: "hoge"}
}

func main() {

    exStmt := getExpressionStatement()

    stmt, ok := exStmt.(*ExpressionStatement)

    fmt.Println(stmt.hoge)
    fmt.Println(ok)

}

演算子*の意味

ところで、実際に型アサーションを行っているstmt, ok := exStmt.(*ExpressionStatement)*はどういう意味なのか。

最初は、式の中なのでポインタ型変数の中身を参照(dereference)しているのか、と思ったがExpressionStatementはポインタ型変数ではない。

アサーションは型名を引数に取ることを思い出すと、構造体のエイリアス型であるExpressionStatementのポインタ型という意味なのだろうか...? (そうかもしれない)

もしくはこの場合の演算子*は既存の規則とは分けて考えるべきなのだろうか。

気になる。

社内でシェル芸入門について話してきた

先日弊社恒例の(ほぼ)社内勉強会であるほろよいてっくでシェル芸入門というタイトルの発表をした。こんな発表をしてはいるが自分もシェル芸初心者。

当日の発表資料はこちら。ちなみに資料ではシェル芸勉強会の過去問及び解答を多く引用させて頂いている。有用な情報を公開してくださっている@ryuichiueda氏に感謝します。

speakerdeck.com

この資料でシェル芸に興味を持ってもらえたら嬉しい。

今回はスライド作成から発表準備含め合計で10時間以内に完了できた。短時間でサクっとできて嬉しい。実は元々このブログの記事にしようと思っていた内容で、既に下書きまで書いていたので調査フェーズを大幅に省略できてその分楽だった。やはり日頃からネタをストックしておくのは大事だなあと思う今日この頃だった。

コードレビューを受ける前に確認しておきたいチェックリストを整理してみた

レビュアーの負荷軽減とレビュー時間の短縮を目的として 、コードレビューを依頼する前に確認しておくべきチェックリストを整理してみた*1

チェックリストは特定の言語・フレームワーク、開発内容(バックエンド・フロントエンド)に依存しない内容になるように心がけてまとめた。

以下チェックリスト

☑ 仕様を満たしているか

  • 予め整理されていた仕様通りに実装されているか
    • 仕様が抜けている・間違っているとコードレビューをしても仕方がない
  • 仕様の不明な箇所を想像で実装していないか
  • 非機能要件(実行環境の差異、性能、大量データ等)を考慮・想定しているか

☑ 仕様が伝わるか

  • 実装した仕様は明文化されているか
    • レビュアーが仕様を理解できないと形式的なコードレビューに終始してしまう恐れがある。必ずドキュメント化するべきというわけではなく、仕様が明らかでレビュアーと互いに合意が取れているのであればチケットで補足する程度でも大丈夫と思う

☑ 影響範囲を記載しているか

  • 機能の追加、不具合修正等既存のコードに手を加えている場合はどこに影響するのかを記載すると、レビュアーの負荷を下げられる
  • 影響範囲が不明なままコードを修正するのは止めよう

☑ 不要なコードが残っていないか

  • 検証用のログ出力や処理、コメントアウトされたまま最終的には使わなかったコード等が残っていないか

☑ 明らかなアンチパターンが含まれていないか

  • 主にパフォーマンスやセキュリティの観点
    • 時間・空間計算量
      • 不要なループ(O(n2)以上)、極端に大きなデータの保持等
    • よく知られた問題
      • N+1問題, XSS...
    • 車輪の再発明 / コードベース内での実装の重複(アルゴリズム、UIパーツ、既に実装されている処理)
      • 言語やOSSのライブラリ、もしくは既存のコードに存在していないか

☑ コードのフォーマットが崩れていないか

  • インデントの崩れ、横幅の超過、不要な空白や改行はないか
    • 人力で修正するのも指摘するのも辛いので自動フォーマットの仕組みを導入するべき(prettier等)

ユニットテストが全てパスするか

  • 新規に追加したユニットテスト、既存のユニットテストを含めて全てパスするか
    • 開発途中までは通っていたはずなのにな...? ということが意外とある
    • 全て実行するにはユニットテストの数が多すぎる場合は、開発箇所と関連する部分を選択する。(影響範囲を特定しきれないという場合もあるかもしれないが、それはそもそもコードを書く前に確認して明らかにするべきこと)

☑ 適切なコメントが書けているか

コメントは後からコードを読む・書く人のために書くものだが、良質なコメントはコードレビューにおけるレビューアーとのコミュニケーションを減らし、レビュー時間を短縮することにも繋がる。

クラス

  • 何のために、何をするクラスなのか
    • 文章化してみると実はクラス設計自体に問題があるということも少なくない

メソッド

園田:一般論として最初に検討するべき候補はあります。「(1)事前条件」「(2)事後条件」「(3)不変条件」「(4)入力の意味」「(5)出力の意味」「(6)副作用」「(7)計算量」です。要するに、利用する側が知っておくべきだけれど、コードの中身を見なければ容易には分からないものです。

Rubyコミッター・Yuguiに学ぶ、コードに書くべき「適切なコメント」と「適切な場所」 - エンジニアHub|若手Webエンジニアのキャリアを考える!より引用。

  • 事前条件、事後条件、不変条件という言葉に馴染みがない人はオブジェクト指向入門 第2版 原則・コンセプトの第11,12章を読み契約による設計の概念に触れてみよう

  • 最近の自分は所謂Javadoc(関数の説明、引数・返値の内容)+事前条件、事後条件、(必要なら不変条件)という形に帰着している

テストメソッド

  • どういったケースにおける何を検証するテストなのか
  • 正常系・異常系のどちらを確認しているのか

制御構文

  • 特にif文の条件式の意図が伝わりにくいことが多い。条件式を自然言語に書き直すわけではなく、なぜその条件式が必要なのかを書く
  • しかし基本的には、伝わりにくいコードをコメントで補うのではなくコード自体を見直すべき。式が長い場合には説明変数に代入できないか、式が簡潔だが意図が伝わらない場合は要約変数を利用できないか?(リーダブルコードの8章を参照)

その他

  • 問題があることを認識しつつも時間がない、今回の修正目的の範囲を超える等の理由で修正しない場合はFIXME、WARNING等のコメントを付与
  • 部分的なリファクタリングで、どうしても過去の負債を引き継がないといけない場合はその旨を書く

*1:コードレビューの定義や仕方はチームや会社によって異なるとは思うが、このチェックリストはそれなりに普遍的な、つまりコードレビューで指摘されずにmasterブランチに含まれると問題があるような内容がほとんどなんじゃないかと思う。