せまい部屋

web/gameエンジニアのweblog

今更プログラミング言語Goを読んだ

諸般の事情につきGoをしっかり復習するべく読んでました。
2016年に出版された翻訳版ということもありGo1.5だったり若干時間を感じてしまいますが、もとよりミニマムな言語仕様で、後方互換も多分問題ない言語ということで現行1.11を触りながらでも何ら考慮することなく読了できました。2018年現在たくさんの使用例が記事として公開されていてその中でも表面的に新しい技術的な取り組みが見えるwebサービス系の事業でよくGoを目にします。が、本書最後にもあるように特定分野に限らずシンプルな言語機能で統一した記述ができるとても良い言語だと思います。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

以下目についた章の雑多な所感

1章チュートリアル

かなり基本的なことが書いてあってこれから学習する人なら写経しながら読解すると良いと思う。Tour of Goとさして変わらないというか、構造体の埋込等にはこの段階では言及されていないので本当にさらう程度に読み進めていけば良さそう。

2章プログラム構造

恐らくどのプログラミング言語でも出てくる基本的な項目、代入や宣言、定義方法、パッケージとファイル作成、参照方法等諸々が懇切丁寧に書いてある。浅く仕事でもGo書いたのである程度理解が及んでる範囲だった。それでもパッケージングにおいてグローバルスコープに定義している変数に対して 変数, err := で代入したときに実は代入されてないなどの記述があり学びがあった。

4章コンポジット型

特にスライスが参照であるみたいな概念がよく理解できるので配列との違いや基本をもう一度理解したいみたいなときに写経していくと良さそうだった。構造体自体は複雑なものでないし問題なく読めたが、埋め込みは他の言語に見たことはなかったので復習になった。実際コードを読む際、埋め込まれている構造体が意味合い的に理解しうるものであれば良いのだが、構造体間の関連が汲み取れない実装を目の当たりにすると苦労しそうな気もする。この辺はクラスベースな言語におけるクラス設計そのものなので言語の表現力というよりも設計/実装力を問われる範囲だと思う。

5章 関数

エラーハンドリングしっかりやりなさいの旨が書いてある。実際Goで実装していくと err != nil 何回書くんだろうと気が滅入ってくるけど、起きうるエラーはインターフェースでありそのapiの振る舞いであるという方針が割と理解できた気がする。このerrorの扱いに関してはなんとなく受け入れて読み書きするよりも思想を理解していることが大事そう。

6章 メソッド

構造体の使用例とメソッド式が地味に学びがあった。構造体の埋め込みは良さそうな使用例をいまいち見たことがなくて単にstruct単位でまとめるというクラス定義の延長という程度のものという理解のもと見ていたけど、以下のような Cache は簡潔に埋め込みの適用例だなーと若干良さを感じた。

type Cache struct {
    sync.Mutex
    mapping map[string]string
}

メソッド式はこれまであまり使用例を見なかったのもあるが、愚直な実装を続ける限り使用することはほぼないが実装表現の幅として知っておけて良かった。

7章 インターフェース

局所的には interface{} でがんばるみたいな印象がある。

  • 比較不能な型が入っていた場合 interface{} 型の比較はパニック起こす
  • 動的な値が nil であってもレシーバはnilでなく if value != nil が意図しない動作となる場合もある

あたりは学びがある気がしていて、割とはまる箇所になりえそう。
実装を一つしか持っていないインターフェースは不要な抽象化ですというのはすごく同意で、実際の業務におけるコードでも如何様な設計思想に則ったのか細かなインターフェースを定義している割に実装は一つで、ユニットテストの実装時に利用してるかと思いきやノーテストでフィニッシュしてるケースを見たこともある。インターフェースの定義に限らずカジュアルに抽象を増やす実装がいけてないという認識は実現場にはないのが実情?

9章 並行性

race conditionとそのたgoroutineについて。競合に立ち向かうためにどうするかという手段として、単一のgoroutineに閉じ込める/相互排他する、という解説。CPUや各コンピュータリソース要因によってgoroutineの並行性を説明する箇所は学びがあった。
メモ化あたりは手を動かさないと理解に至らない感じあるので実践してきたい。goroutineとOSスレッドの差異はスタックサイズ、スケジューリング、GOMAXPROCSの影響とかがさらっと書いてあってあとでググって深掘りしたい。

11章 テスト

ランダムテストって必要なのか…?という感じもあるけど一応紹介されてるのでそういう手法もあるという理解で良さそう。実際仕事のコードでランダムテスト実装してPR送っても必要なのか議論になりそうな所感もある。ホワイト/ブラックボックステスト、あたりの話はテスト実装の技法としてprivateメソッドの実装をどこまでかっちりやるかとかモック実装方法(ライブラリ導入含め)、テスタビリティを考慮した設計等を問われる話しにもなるのでなかなか問の多い箇所だと思う

リフレクションその他

これまで低レイヤーに潜るような実装は避けてきているつもりなのでreflectパッケージとか無縁なわけで、というかアプリケーションを書くというコンテキストに置いてはそれで最善であるという信念すらあるわけですが、特段使い所を見極めて必要に応じて効果的にリフレクションを使えるようにはなりたいなーというモチベーションもワンチャンあることもあります。そんな稀有な機会のために必要事項を解説してある。周知の通りなんでもできるので下手に手を出すより先既存ライブラリやオフィシャルの実装コードを見て利用例を学ぶのが得策かなーという印象。
リフレクションを用いることでコンパイラを頼りにできなくなるし、静的型付き言語たるGoを選択したモチベーションから離れてしまう認識は必要そう。章末尾に記述されている通り。

docker-compose間でボリューム共有

めも。前任者から引き継いだ趣味プロジェクトがdocker-composeでなにひとつローカル環境が立ち上がれないぶん投げだったので対応してみてるやつ。別docker-composeで、リポジトリAから参照/保存/更新してるelasticsearchをリポジトリBから参照するみたいな感じで、やりたかったことはボリュームの共有

マウントでホスト側のファイルパス書いてしまうような記述でもおそらく要求を満たすことはできるのだろうが、一応別リポジトリなので謎定義になるのがあれなのと、どうせなら名前付きボリュームでパスなど関知せずに済ませたい。と思ったら external なるいい感じのオプションあった

https://docs.docker.com/compose/compose-file/#external

リポジトリA、B共に同じvolumes指定

services:
  ...
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch
    ports:
      - 9200:9200
    volumes:
      - esdata:/usr/share/elasticsearch/data

volumes:
  esdata:
    external: true

ローカルにボリュームがない場合先に作る

ERROR: Volume esdata declared as external, but could not be found. Please create the volume manually using `docker volume create --name=esdata` and try again.
$ docker volume create --name=esdata
esdata

volumesはバージョン3.2から少し凝った記述もできる模様。tmpfsとかはあんまり用途なさそうだけど… https://docs.docker.com/compose/compose-file/#long-syntax-3

今回は小規模&複雑でなし構成なのでリポジトリ一つでモノリシックに詰め込む方針を推したい感想はあるけどさておき動作させてく

ActiveStorageのreturnにはまっためも

どうにもテストが落ちてしまって地味な検証を要した

tl;dr

ActiveStorageで has_many_attached マクロを設定している場合、model.attach(ファイル)の戻り値はActiveStorage::Attachmentの配列。has_one_attached マクロを設定していると model.attach(ファイル)のreturnはnil

railsguides.jp

起きたこと

Trailblazerなんか使っているので一つずつステップを記述していて、その一つに、ActiveStorageへのファイルアップロードがありました。以下のような感じ。

class User < ActiveRecord::Base
  ...
  has_many_attached :photos
  has_one_attached :icon
  ...
end
class MyOperation < Trailblazer::Operation
  ...
  step :save!
  ...

  def save!(options, user:, **)
    user.icon.attach(options[:params][:file])
  end
end

運良く事前に :photos に対する多数のファイル添付が可能なように実装されていたので、仕様に沿って今回は1対1となるような has_one_attached マクロを設定。比較的似たような処理を書くもののどうにもrequestのスペックが通らず…

結果

今回のケースだとstepに指定している save! メソッドがtruthyであればいいけどずっとnil返しててしんでいました。実地で戻り値を確認していくと、attachメソッド返り値が違うぽくて、多数添付できる場合は [#<ActiveStorage::Attachment id: ...>] 、1対1の場合はnilになってfalthyになってました。事前に実際リクエストして試したのも裏目に出て余計発見できませんでした😞
attach自体は意図した動作であることを確認して、現状は以下の感じで確認するように改修しています。

  def save!(options, user:, **)
    user.icon.attach(options[:params][:file])
    user.attached?
  end

LL久しぶりだけどRailsとその周りのツール群はどうにもメタなことやりすぎてる感じが難しい。