読者です 読者をやめる 読者になる 読者になる

コードの格付け++

  • ランクA ~ コメントを読まなくても、関数名だけで使い方がわかる
  • ランクB ~ コメントを読めば使い方がわかる
  • ランクC ~ 中身を読まないと使い方がわからない
  • ランクD ~ 書いた人の心を読まないと使い方がわからない(or 心を読んでもわからない)

元ネタ:[開発]ランクCのゴミコードを生産し続けないために、、、

広告を非表示にする

レガシ―プロジェクトを引き受けた時、最初に準備するべき七つのこと

「営業一課で使っている■■ってPHPアプリがあるんだけど、メンテナの○○さんが退職するから、4月からは君に任せるよ」

そして、ソースを見てビックリ!!

テストは無い、リリース時の手順書も無い、仕様書ももちろん無い。

一方で「ログインできません」「なんか『Internal Server Error』って出てます」「表が崩れます」という不具合報告の数々。

ここで、プレッシャーに押しつぶされて「とりあえず、簡単な不具合から早く直そう!」と思うかもしれませんが、ちょっと待った!その前にやっておくべきことがあります。

※この記事でイメージしているのは「社員25人に使われている」という程度のレガシーアプリです。「3度の会社合併を経た40年もののCOBOL製基幹システム」には役立たないかもしれません。

1. 協力者を確保する

以下の人たちを確保してください。他のプロジェクトと兼業で構いません。

  • アプリの仕様を知っている人(前任者 or ユーザー)
  • コードレビュワー
  • テスト担当者
  • インフラ担当者

コーディングは自分でやるとしても、専門外の分野では無理せずその道の人に頼りましょう。 相談できる人がいると大いに助かります。

また、ユーザーや偉い人と交渉する時に、1人より5人の方が交渉しやすいでしょう。

2. 仕様をドキュメント化する

ユーザーに聞いたり、コードを読んだりして、アプリの仕様をドキュメントにしましょう。

ここでは、完璧な仕様書を目指す必要はありません。 正確性・網羅性よりも、ともかくドキュメントを作ることが大事です。

というのも、ドキュメントが無い状態だと、みんな想像で物を言うので、議論が迷走しがちになるからです。

ドキュメントという形に結晶化すれば「ここの記述は実態と違う」とか「ここを変えましょう」と、生産的な議論ができるようになります。

3. 手動テストをドキュメント化する

最終的には各種自動テストでカバーするべきですが、最初は手動でテストしなければいけません。しかし、レガシープロジェクトの場合は、手動テストもロクに無いことがあります。

とにもかくも、既存のテストケースを、ドキュメントに起こしてください。 TestRailなどの管理ツールが望ましいですが、Excelでも構いません。

テストもドキュメント化しなければ生産的な議論はできません。

  • どの部分のテストが足りていないか?
  • テストケースが重複していないか?
  • ユニットテストで確認できる内容ではないか?

等など。

また、テストケースが一覧になっていれば、テストにかかる工数を正確に見積もれるようになりますし、 必要に応じて複数人で分担して実施できるようになります。

4. Jenkinsを導入する(CIツールを導入する)

レガシープロジェクトでは常に時間が足りなくなります。

Jenkinsからボタン1個で、ビルド、ユニットテスト、リリース、コーディング規約チェックなどの定形作業をできるようにしてください。Jenkinsはシェルスクリプトをコピペするだけでジョブが作れます(そもそも、定形作業がスクリプト化されていないこともありますが・・・)。

なお、別のCIツールでもよいですが、Jenkinsは汎用的でお手軽なので、とりあえずJenkinsを使うのがベターでしょう。

5. コーディング規約をチェックするツールを導入する

さて、ようやくコーディングの準備が整ってきました。しかし、タブとスペースが混在したようなコードを編集しようとしても、ストレスが溜まりますし、つまらないミスも犯してしまいます。

コーディング規約を定め、全てのコードが規約に従うようにしてください。

ここで、どのコーディング規約を採用するかはあまり重要ではありません。 規約をRubocopやCheckstyleのようなツールで自動的にチェックできることが重要です(コードレビューの際にスタイルについて指摘する/されることほど不毛なことはありません)。

むしろ、ツールに合わせて規約を選択するくらいでよいと思います。

ツールは以下の基準で選んでください

  • 簡単に使えること
  • 簡単な内容(スペースなど)は自動修正できること
  • チェック対象にしないファイルを指定できること(全ファイルを一度に書き直すのは時間がかかるので、あまり変更しないファイルを後回しにできるとよい)

また、ツールにはスタイルを細かく設定できるものもありますが、時間の無駄なので、デフォルト設定のまま使いましょう。

6. 便利な新言語(less・scss・haml・slim・TypeScript・Kotlin など)を導入する

WEBアプリであれば、大量のHTML・CSSJavaScript のソースが含まれているはずですが、 これらは決して書きやすい言語ではありません。

書きにくい言語と格闘してもしょうがないので、less・scss・haml・slim・TypeScriptなどを導入してストレスを減らしましょう。これらの言語はHTML・CSSJavaScriptの上位互換なので、拡張子を変える以外の作業は要りません。

また、alt-JavaのKotlinは、文法的にはJavaの上位互換ではありませんが、Javaから自動変換することができます。

なお、拡張子の変更・自動変換であっても、変更の後にはテストが必要になりますが、それは他の変更のテストのついでにやればよろしい。

7. (まともな)テスト環境を(複数)用意する

まれによくあることですが、サーバーが本番環境の1台しかなく開発者のマシンで適当にテストした後いきなり本番適用する(そしてバグる)。

あるいは、テスト環境と称する環境はあるが本番環境と設定が違ってしまっているので、テストがテストになっていない。

本番環境とできるだけ同じ設定のテスト環境を作りましょう。 その際にAnsibleなどを使うと後々便利ですが、ともかく「テスト環境が無い」では話にならないので方法にはこだわらずに急いで用意しましょう。

また、テスト環境は複数作っておくべきです。後々、テスターの人数が増えたり、自動テストを導入したりしたときには、テストを並列で行うことになるからです。

終わりに

ユーザーや上司からは、とっとと不具合を直せと言われるかもしれません。

しかし、戦いには準備が必要です。 初見のコードにいきなり手を付けても、直しきれなかったり、エンバグしてしまう可能性大です。

また、メンテは長丁場です。あなたはきっと、何年もメンテを続けなければなりません。 ですから、最初の時点で、作業する時のストレスを減らす手立てを講じてください。

広告を非表示にする

Scalaの最も難しいところ

仕事でScalaで書いたプロジェクトを担当する機会がありました。

Scalaは難しい」と時々言われます。

私の感想は やっぱりScalaは難しい

しかし、それはScala大好きな人たちが想像するのとはちょっと違うところ。

続きを読む
広告を非表示にする

「サービス層」「DAO層」はアンチパターンだ

転職して、Java(Jersey)とScala(Play)で書かれたWebアプリケーションをメンテを1年半ほどする機会があったのですが、Java界隈で特によく使われている(ような気がする) 「Resource / Service / Daoの3層に分ける設計」アンチパターンであると思えてならないので、思っているところを書き出してみます。

続きを読む
広告を非表示にする

direnvで、使用するdocker-machineを指定する

.envにこんな風に書けるようにします。

# .env
use docker-machine my-rails-app

ディレクトリに移動すると、自動的にdocker-machineを(まだ起動していないなら)起動し、docker-machine env環境変数をセットします。

$ cd ~/Documents/my-project
direnv: loading ~/.direnvrc
direnv: loading .envrc
direnv: using docker-machine my-rails-app
Starting "my-rails-app"...
Machine "my-rails-app" is already running.
direnv: export +DOCKER_CERT_PATH +DOCKER_HOST +DOCKER_MACHINE_NAME +DOCKER_TLS_VERIFY
$ docker ps # my-rails-app のコンテナ一覧が表示される

インストール

git clone git@github.com:doloopwhile/direnv_use_docker-machine.git ~/direnv_use_docker-machine
echo '. ~/direnv_use_docker-machine/direnv_use_docker-machine.sh' >> ~/.direnvrc

使用目的

docker-machineではdefaultホスト1個しか立ち上げていない人が多いと思います。

docker-composeなどを使えば、コンテナ名が被らないようにできるので、 それで困ってないのかもしれません。

しかし、プロジェクト毎にDockerホストを立ちあげると便利なことも。

  • ポート番号がかぶらない(複数のWEBアプリを同時にdocker-compose upさせておける)
  • ホストごとにDocker Engineの設定を変えられる
  • docker psなどのリストがスッキリする
  • 混乱した時はdocker rm -f $(docker ps -a -q)してリセットできる。

反面、イメージをホスト毎にインストールする手間などもあります。

Docker開発はまだ試行錯誤が多いのですが、この方法も案外便利かもしれませんよ。

広告を非表示にする

Yggdroot/indentLineのせいでmarkdownの表示がおかしくなった

markdown編集中に"*hogehoge"が"hogehoge"のように変換されて表示されてしまう。この表示の仕方はVimのmarkdownモードの機能の1つで、set conceallevel=2が設定されているとこう表示する。

問題はset conceallevel=2などした覚えが無いということで、調べるとどうやら"Yggdroot/indentLine"が内部でset conceallevel=2をしているようだ。勝手なことをするなよな。

広告を非表示にする

Ruby初心者(つまり昨日の僕)に伝えたいRubyの7つのハマり所

書くのが楽しいプログラミング言語であるRubyですが、それでも「落とし穴」がいくつも存在します。

その中から私が最近ハマった落とし穴を7つ紹介します。

シンボルと文字列は異なる

Rubyには「文字列(String)」("a" とか 'Hello world!')の他に、文字列に似た「シンボル(Symbol)」(:aとか:hello_worldとか)があります。*1。両者は異なる型なので、例えば 'a' != :a となります。

そのため、キーがシンボルであるハッシュ(Hash)から、文字列を使って値を取り出そうとするとnilが返されます。

h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

p h['ja'] # => nil
p h[:ja] # => "日本語"

ちなみに、シンボルはハッシュのキーとしてよく使われるため、=> を使わない短縮記法があります。

# これは・・・
h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

# こうも書ける
h = { ja: '日本語', en: '英語', fr: 'フランス語' }

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*2。両者は異なる型なので、例えば 'a' != :a となります。

ハッシュのキーにはシンボルがよく使われますが、例えばRailsparams(HTTPのクエリパラメータ)のキーは文字列です。 したがって、文字列を使ってparamsから値を取り出そうとするとnilが返されます。

class XYZController
  def search
    params[:lang] # => nil
  end
end

なお、Railsはハッシュのキーをシンボルに変換するHash#symbolize_keys等の一連のメソッドを追加します。

また、JSON.parseにはJSONのキーをシンボルに変換するsymbolize_namesパラメータがあります。

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*3。両者は異なる型なので、例えば 'a' != :a となります。

そのため、シンボルを文字列と連結したり、文字列のメソッドを呼びだそうとするとエラーになります。

a = 'hello'
b = :world

a + b # => TypeError: no implicit conversion of Symbol into String

b.tr 'o', 'x' # => NoMethodError: undefined method `tr' for :world:Symbol

このような時は.to_s.to_symで、文字列とシンボルを相互に変換します。

また、文字列連結の代わりに式展開を使うこともできます。 式展開はシンボルだけでなく数値や他の型つかえますし、文字列連結を使うよりも可読性が高くなります。 そのため、Rubyでは文字列連結を使う機会はあまり多くありません。

# これよりも
a + ' ' + b

# こうするべき
"#{a} #{b}"

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)が・・・すみません、これで最後にします。

method_missingの引数はシンボルです。実際のところ、シンボルに文字列操作を施す機会はメタプログラミング以外はあまりありません。

なので、method_missingでこんなコードを書くとエラーになります。

def method_missing(name, *args)
  # エラーになるコード
  if name.end_with?('!')
     # ...
  end
  super(name, *args)
end

privateメソッドをクラス外から呼び出すとmethod_missingが呼ばれる

黒魔術だなんだと言われつつ使わざるをえないmethod_missing について。実はmethod_missingが呼ばれるのはメソッドが無い場合だけではないとことです。

呼ぼうとしたメソッドprotectedprivateであった場合もmethod_missingは呼ばれます。

class Foo
  def method_missing(name)
    p name
  end

  private

  def greeting
    p 'hello'
  end
end

foo = Foo.new
foo.greeting # => :greeting

だから、method_missingは黒魔術なのであって、method_missingなんて使うのは止めましょう。

Module#define_method が作るメソッドはpublicになるとは限らない

黒魔術でもうひとつ。メソッドを動的に定義するdefine_methodは、privateの後ではprivateなメソッドを定義します。

class FooController
  def index
    # 普通のメソッド
  end

  private # あとから foo_params を追加!

  def foo_params
    #...
  end

  %w(name email age).each do |name|
    define_method "show_#{name}".to_sym do
      # ...
    end
  end

なので、「ゴチャゴチャした処理は後ろに書こう」とクラスの後ろでdefine_methodを書いていて、 途中にprivateなメソッドを追加したりすると、思わぬ動作になります。

ちなみに「privateの後ではprivateになる」のはattr_readerなども同様です。

Structはキーワード引数を取らない

最後に、真面目に罠だと思うことを。

たまに便利なStructは、実はコンストラクタにキーワード引数を使えないことです。 間違ってキーワード引数を使うと、それらは1個のハッシュを渡したかのように扱われます。

Dog = Struct.new(:name, :age) # => Struct::Dog

# キーワード引数は使えない
Dog.new(name: 'ポチ', age: 1) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# ハッシュを渡したかのように扱われる。
Dog.new({name: 'ポチ', age: 1}) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# なのでStructでは通常の呼び出しをするしかない
Dog.new('ポチ', 1) # => #<struct Struct::Dog name="ポチ", age=1>

正直な所、これは仕様のバグだと思います。後方互換性が失われるので直しにくいと思いますが。

*1:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*2:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*3:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

広告を非表示にする