ruby-style-guideを読んだら知らないことがイロイロあって勉強になった

2年ぶりに業務でRubyを書きはじめたので、改めてruby-style-guideを読んだら知らなかったことがイロイロあったのでメモ。

2019/02/28当時の日本語版: 2bac495を読みました。

1. 本文のないクラスは1行のフォーマットを用いましょう。

# 悪い例
class FooError < StandardError
end

# 悪くない例
class FooError < StandardError; end

# 良い例
FooError = Class.new(StandardError)

「悪くない例」を書きがちだったので。

2. forは、どうしても使わなければいけない明確な理由が明言できる人以外は、使ってはいけません。 多くの場合は代わりにイテレータを使うべきです。 forはeachをつかって実装されています(だから、より遠回しです)が、 forは(eachと違い)新しいスコープを導入せず、 そのブロック内で定義された変数は、ブロックの外からも見えます。

arr = [1, 2, 3]

# 悪い例
for elem in arr do
  puts elem
end

# elemはループの外からも参照できることに注意しましょう
elem # => 3

# 良い例
arr.each { |elem| puts elem }

# elemはeachブロックの外からは参照できません
elem # => NameError: undefined local variable or method `elem'

なんとなく、「Rubyでfor文は使わないものだ」という認識はあったし、実際for文は使ったことがなかったけど、なんで使わないのか、避けるべき理由は何なのかを意識していませんでした。

↑の例に書いてあるようにfor文内でつかった変数が、外に漏れ出てくるのはなんかのきっかけでハマりそうだし、やっぱりfor文は避けるべきなんだなと思いました。

3. あまりに暗号めいている String#% メソッドよりも sprintfformat を使いましょう。

# 悪い例
'%d %d' % [20, 10]
# => '20 10'

# 良い例
sprintf('%d %d', 20, 10)
# => '20 10'

# 良い例
sprintf('%<first>d %<second>d', first: 20, second: 10)
# => '20 10'

format('%d %d', 20, 10)
# => '20 10'

# 良い例
format('%<first>d %<second>d', first: 20, second: 10)
# => '20 10'

単純に String#% の存在を知らなかったです。 (推奨されてないし、あえてこれから使おうとは思わないですが)

4. 名前付きフォーマット文字列を使用する場合、 %{name} よりも %<name>s を使いましょう。 %<name>s は値の型に関する情報をエンコードするためです。

# 悪い例
format('Hello, %{name}', name: 'John')

# 良い例
format('Hello, %<name>s', name: 'John')

どっちの記法も知りませんでした。

5. あまりに暗号めいている Array#* メソッドよりも Array#join を使いましょう。

# 悪い例
%w[one two three] * ', '
# => 'one, two, three'

# 良い例
%w[one two three].join(', ')
# => 'one, two, three'

Array#* を知りませんでした。(使わないけど)

6. size の代わりに count を用いてはいけません。 Array 以外の Enumerable オブジェクトでは、 count を使うと要素数の計算のためにコレクション全体を走査してしまいます。

# 悪い例
some_hash.count

# 良い例
some_hash.size

これも知りませんでした。

標準ライブラリはこのルールに従うかもですが、ActiveRecord とか外部ライブラリだとまたルールが異なってきそうなので、結局のところdocumentなりソースなりをちゃんと読むべきな気がしました。

7. Struct.new の使用を考えましょう、 それは、単純なアクセサ、コンストラクタや比較演算子を定義してくれます。

# 良い例
class Person
  attr_accessor :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end

# より良い例
Person = Struct.new(:first_name, :last_name) do
end

Struct.new にブロックを渡せることを、知りませんでした。

こういうふうに使えそうですね。

Person = Struct.new(:first_name, :last_name) do
  def full_name
    "#{first_name} #{last_name}"
  end
end

p = Person.new('Hoge', 'Fuga')

p.full_name
#=> "Hoge Fuga"

ただ、メソッドを用意する必要があるような場合は Classを使うような気もするので、使いどころが多いかは分からないです。

8. 例外は fail より raise を使いましょう。

# 悪い例
fail SomeException, 'message'

# 良い例
raise SomeException, 'message'

「自分が例外を射出するときは failrescue などで補足した例外を再射出するときは raise」のような使い分けがあった気がしましたが、今は全部 raise で良いようでした。

9. きちんとインデントされた複数行の文字列には、Ruby 2.3 の、インデントされた ヒアドキュメントを使いましょう。

# 悪い例 - Powerpack の String#strip_margin を使用しています。
code = <<-RUBY.strip_margin('|')
  |def test
  |  some_method
  |  other_method
  |end
RUBY

# こちらも悪い例
code = <<-RUBY
def test
  some_method
  other_method
end
RUBY

# 良い例
code = <<~RUBY
  def test
    some_method
    other_method
  end
RUBY

<<~ で書くヒアドキュメントを知りませんでした。

10. キャプチャした結果を使う必要のないときは、キャプチャしないグループを用いましょう。

# 悪い例
/(first|second)/

# 良い例
/(?:first|second)/

これも単純に知らなかったシリーズ


まだまだ知らないことが多く、たまに読み返すと学びがあって良いなと思いました(小並感)

C++で書いたプログラムを、docker-composeで立ち上げたUbuntu 18.04で動かして、macOS上のCLionからデバッグする。

タイトルのとおりです。

(普通にkqueueで良いんじゃないのと思いはしたものの、)Linuxのepollシステムコールを使ってソケット処理を書こうとするとどうなるのかを試してみたかったのですが、手元にあったマシンがmacOSだったので、ちょうど最近業務でも使い始めたがまだ慣れていないdocker-composeで開発環境を用意してみようとやってみました。

軽い気持ちでやり始めたものの、なんだかんだで結構ハマったのでメモを残します。

やろうと思えば、docker-composeの操作もすべてCLion上で完結できるようですが、なんかうまくいかないことがあったので、docker-compose startとかはコンソール上でやって、デバッグの部分だけCLionでやってます。

(一応公式ヘルプに使い方 Docker - ヘルプ | CLion が書いてありましたが)

また、以下のソースはリポジトリ GitHub - rkmathi/dockerfiles に置いてます。

動作環境は

な感じです。

とりあえず、CLionなのでまずは CMakeLists.txt を用意しました。

cmake_minimum_required(VERSION 3.0)

set(CMAKE_VERBOSE_MAKEFILE OFF)

set(out_dir ${CMAKE_BINARY_DIR}/out)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${out_dir})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${out_dir})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${out_dir})

project(work)

enable_language(CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CXX_EXTENSIONS OFF)
ADD_COMPILE_OPTIONS(-Werror -Wall -Wextra)

add_executable(work main.cc)

main.cc も最初なので適当です。

#include <iostream>

int main() {
  std::cout << "main.cc" << std::endl;
  return 0;
}

Dockerfile は、イロイロなところを見ながら次のようにしました。 流石にプロダクション環境で使うことは全く想定していないので、rootユーザにパスワードログインしちゃいます。

FROM ubuntu:18.04

# Configure working directory
ADD . /work
WORKDIR /work

# Install packages
RUN apt-get update && \
  apt-get upgrade -y && \
  apt-get install -y build-essential cmake gcc g++ gdb gdbserver openssh-server rsync

# Enable ssh for root user
# see also: https://docs.docker.com/engine/examples/running_ssh_service/
RUN mkdir /var/run/sshd && \
  echo 'root:root' | chpasswd && \
  sed -i 's/#\?PermitRootLogin prohibit-password/PermitRootLogin yes/'   /etc/ssh/sshd_config && \
  sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile

# Expose port for ssh & gdbserver
EXPOSE 22 7777

# Run sshd
CMD ["/usr/sbin/sshd", "-D"]

docker-compose.yml は、こんな感じです。 本当は version: '3'の部分はマイナーバージョンまで指定したほうが良いのかもしれないですがまだ良くわかってないので一旦適当になってます。

version: '3'
services:
  ubuntu-gdbserver:
    build: .
    security_opt: # For gdb debugging
      - seccomp:unconfined
      - apparmor:unconfined
    ports:
      - "7776:22" # For ssh
      - "7777:7777" # For gdbserver
    volumes:
      - .:/work

1. ↑で用意したubuntu-gdbserverサービスを立ち上げておく。

$ docker-compose build ubuntu-gdbserver
$ docker-compose start ubuntu-gdbserver

この後、 ssh root@localhost -p 7776 を試してSSHできることを確認した。 (パスワードは↑のままセットアップしたなら root )

2. CLionを開いて、Docker integrationプラグインをインストールする。

3. Preferences → Build, Execution, Deployment → Toolchainsから追加する。

Nameは何でも良いが、Nameの隣の入力欄は Remote Host にする。

4. Preferences → Build, Execution, Deployment → CMakeから追加する。

ここもNameは何でも良い

5. CLion上で適当にbrake pointをはる

6. Debug実行するときのターゲットで、さっき作ったToolchain選択する。

f:id:rkmathii:20190501155103p:plain

7. Debug実行すると、ちゃんとremote debugできるようになる。

f:id:rkmathii:20190501155231p:plain

おわり

転職しました

2015年4月に新卒で入社してから丸4年間はたらいていた株式会社ディー・エヌ・エーを先月退職しました。

研修後の1年半くらいは、RailsSinatraでモバイルゲームのBaaSを開発・運用するチームにいました。

その後の2年ちょっとは、C++でモバイルゲームのリアルタイム通信基盤を開発・運用するチームにいました。

前半と後半で扱う技術がぜんぜん違う(HTTP↔TCPRubyC++、ステートレスREST↔ステートフル常時接続、オンプレ↔クラウドとか)ので、とても勉強になったし面白かったです。

今日からは、株式会社トレタで頑張ります。

corp.toreta.in

まずは2年ぶりにRubyの勘を取り戻すところから

ISUCON7「Railsへの執着はもはや煩悩(ry」で本戦4位だった

2017/11/26に、「railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。」(@cnosuke, @k0kubun, @rkmathi)というチームで、ISUCON7の本戦に参加して4位でした。

www.instagram.com

最終スコアは27,304でした。

毎回取る余裕がなかったのでかなり中途半端ですが、気づいたときにかいたスコア置き場はこれ

https://gist.github.com/rkmathi/73630698d4350882288599497a96d759

cnosukeさんが公開してくれた、本戦のリポジトリはこれ

https://github.com/cnosuke/isucon7-final

k0kubunがとても詳しく書いてくれたので、具体的にどういう手を打ったのかとかはそちらを是非

k0kubun.hatenablog.com

もう一人のcnosukeの記事はこちら

cnosuke.hatenablog.com


チーム名の通り(?)、ISUCON3から毎回ずっとRubyで参加してましたが、今回の本戦は途中でGoに切り替えました。

予選と同じように、基本的には「計測→ボトルネックを潰す→計測→ボトルネックを潰す」をひたすら繰り返す感じでした。

自分はGoを使ったことなかった( Rubyもここ1年くらい使ってない )のですが、とにかく計測するためにpprofを導入したりしてました。

github.com

これをするだけで計測ができて、しかも関数内のボトルネックがぱっと見えたり、結果をSVG画像で吐かせたりできて便利でした。

こんな感じ

gist.github.com


ISUCON3から毎年参加していて、ISUCON4では学生枠で本戦行けたもののISUCON5,6はダメだったので、久しぶりに本戦に行けて嬉しかったです。

最後になりましたが、運営の皆さま本当にありがとうございました!!!

ISUCON7「Railsへの執着はもはや煩悩(ry」で予選通過した

2017/10/22(日)に、「railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。」(@cnosuke, @k0kubun, @rkmathi)というチームでISUCON7の予選に参加しました。

最終スコア217,457で予選通過できました:joy:

ISUCON3から毎年参加していてましたが、社会人になってからは1度も予選通過していなかったので普通に嬉しかったです(小並感)

やったこと

予選のリポジトリはこれ⇒GitHub - cnosuke/isucon7

k0kubunが書いてくれた記事( ISUCON7予選2日目「Railsへの執着はもはや煩悩」で予選通過した - k0kubun's blog )が詳しいです。

構成はこんな感じで、3台のうち2台を前段において、残り1台はMySQLをするだけのサーバにしました。 f:id:rkmathii:20171023085756p:plain

前回や前前回や前前前回と同じ感じで、主に「k0kubunがガリガリアプリコードを直す・cnosukeがインフラ周りを整備する・僕が雑多な何かをする」みたいな役回りだったと思います。

  • とにかく隙きあらばNew Relicで計測
  • h2o(with mruby)やRedisをインストールした (が結局使わなかった)
  • 後半のCPU負荷がGzipに集中していたので、Gzip以外の圧縮形式にしたりそもそも圧縮しないことも考えたが、結局グローバルの帯域が100Mbpsだったので圧縮レベルを1に下げた
  • 終盤はベンチマークのEnqueueボタンを連打する係

とかしてました(あんまりドラスティックな変更はできてない)

今回の予選は、ベンチマークが何度でも気軽に試せた(最後の方は若干帯域がサチってた?)のでとてもやりやすかったです。

本戦も頑張ろ〜