はじめに
(プログラムで)何かを新しく作る時に僕が考えている事になります。
今回はRubyスクリプト
(ソースはこちら)を例に出しますが、Ruby
以外でも参考になると思います。
※本記事はあなたのRailsアプリはいくらの解説編にもなってます。
作りたいものを考える
初めのステップとして必要な事は何を作るか?
になります。
これは頑張って考えるというよりも、僕の場合は突然作りたくなる
ケースがほとんどです。
とりあえず作ってみる
作るものが決まったらとりあえず作ってみる事です。
プログラムの書き方が正しいかどうかは二の次で、どんな糞コードでも良いので作りましょう。
作ったものが形になった時には達成感を得られます。
また反省ができて、次のステップに進む事ができます。
うまく行けばサービスとして世の中に公開できます。
ディレクトリ構成を考える
プログラム初心者の人はとりあえず作ってみる
とやってみても失敗する事の方が多いです。
そもそもモノが出来上がらなかったり、最初の一歩でつまづいてしまう事もよくあります。
また作ったプログラムを改修しようとしても、どこを直せば直るかわからないような酷いコードになっている事も多いです。
ただし、それでも一度自分一人でとりあえず作ってみる
を行うことによって見えなかったものが見えてくるようになります。
ここから先を読む前に一度とりあえず作ってみる
を実践してみると良いでしょう。
今回僕が作ったプログラムを元に作る時に必要になる構成を考えてみます。
このプログラムは簡単にいうと以下のようなフローを進んでいきます。
- how_much.rbがトリガーとなって動き出します。
- dispatch.rbがRailsプロジェクトからファイルを取り出します。
- 取り出されたファイルをprogram.rbが解析します。
- page.rbでHTMLを作成します。
このような一連の処理を行う上で重要となるのは、どこで何をさせるかです。
フレームワークを学ぶ
フレームワークは使う時にも役に立ちますが、何かを作る時の参考としても役に立ちます。
構成を考えるときにゼロベース
で考えると新しいことを発見できる場合もありますが、大抵においては先人の知恵にはかないません。
そういった時には先人の知恵
であるフレームワークを参考にすると良い構成を作りやすいです。
今回のプログラムを見てもらうとわかると思いますが、基本的な構成はRails
を参考にしてます。
app
などのディレクトリはないですが、Rails
の構成をフラットにしていることがわかると思います。
このように分けておくことで、後々思ったよりも大きなプロジェクトに発展した時にある程度まで許容できるプログラムが出来上がります。
なので、まずは人が作った構成を真似てプログラムを配置するディレクトリ構造を考えましょう。
そして、間違ってても良いのでディレクトリ構成を先に作りましょう。
何が重要なプログラムになるか見極める
あなたのRailsアプリはいくらを作る時、真っ先に浮かべたのはDispatch
のプログラムです。
このプログラムの性質上、ルートディレクトリから全てのディレクトリを舐めていく必要があります。
そのあとにファイル毎に金額を計算する必要があると思ったので、肝となるプログラムはDispatchクラスになると思ったのでこのプログラムを軸に構成を組み立てました。
今回のプログラムは1日足らずで作成した小さなものでしたが、大きなプログラムを作る時にも考え方は同じです。
肝となる部分がどこかを見極める事はプログラムを作る上で重要なポイントになります。
重要な部分は計算部分ではないのか?
今回のプログラムでいうとProgramクラスが中核を担っているようにも見えますが、このプログラムは呼び出されるクラスであるためdispatch
とインターフェースを合わせれればどんな形でも動きます。
なので、僕はRailsプロジェクトを舐めていくdispatch
の方が重要でありprogram
部分はなんとでもなると判断しました。
もし、program
から作った場合は、具体的なインターフェース部分がぼやけた状態で作成するため完成までもう少し時間がかかったでしょう。
(できない事はなかったと思います。)
ただし、プログラムが大きくなればなるほど重要なプログラムは増えていきます。
今回のように簡単にはいかないと思いますが、初めの一歩としては小さなプログラムの中で重要となるプログラムを見極める力を培うのは重要な事です。
ボトルネックを事前に調べておく
今回のような小さなプログラムではあまりボトルネックが見当たりませんでしたが、重要なプログラムを見極めるのと同様にボトルネックを見極める事も重要になります。
今回でいうと以下の部分がボトルネックになりそうだったので事前に軽く調べておきました。
スクリプトの引数を受け取る方法
僕はRuby
といえばRails
でやってたので、スクリプトの引数を受け取る方法を知りませんでした。
ARGV[0]
$0
でプログラム名も受け取れるようです。($1
で第一引数を受け取れるという記事も見たのですが、僕が確認した限りでは受け取れれませんでした。)
ERBの使用可否
最後にHTMLを出力する際にヒアドキュメント
で実装するのは厳しいと思ったので、事前に使えるかどうかだけ確認しました。
結果、使える事が分かったので調査はそこで終わらせてあとは実装時に検証して作りました。
require 'erb'
erb = ERB.new(File.read(Erbファイル))
# bindingは変数ではなくメソッドです。
p erb.result(binding)
requireに関して
Railsの場合はオートロード
でファイルが読み込まれるので、自前でrequire
を行うときはGem
を使う場合などだけでした。
なので、事前に「どのようにファイルを読み込めるのか?」と「実行パスが違っても実行できるのか?」を調べておく必要がありました。
また、ディレクトリ内部のrb
ファイルをすべて読み込めるようにしておかないとrequire
だらけになると思ったのでこちらも調べておきました。
些細な疑問
require
についてはRailsのプログラムが参考になったのですが、根深いところはわかってません。
その中で一つの疑問があります。
継承関係を持つクラスの場合は親クラスの方を先にrequire
しておく必要があります。
しかし、Rails
では親クラスを先に読み込む指定をしなくても実行時エラーは発生しません。
なぜなんでしょうね・・・?
これらを事前に調べた理由
今回のプログラムは難しい事はなく、ボトルネックなど調べる必要はなかったのですが、、、1日で実装するつもりだったので、可能であるかどうかを知るために事前に調べておきました。
実際に現場でアーキテクチャーとして働く場合はボトルネック
を先に調べないと、最後の最後に悲劇が待っている場合もあります。
(最後に実現不可能な事に気付き、泥臭い方法で回避するような局面を迎えるでしょう。)
一連の処理を作成する
ここまで出来たら一連の成功処理を作成しましょう!
イレギュラーなパターンは後で肉付けをしても問題ありません。各ディレクトリに一つしかファイルがなくても構いませんので一連の処理を作成します。
僕の場合はこのような形で一連の処理となる部分を作りました。
Railsプロジェクトを舐めて表示する
ここまでをまず作り、ファーストコミットとしました。
後はパーサー部分を作成すれば良いという状況に持っていきます。
Rails
アプリでいうなら紙芝居(画面遷移だけで、処理は行わない状態)を作ったタイミングに似てます。
主要処理を作成する
ここまで出来たら主要処理の部分のインターフェースを考えて実装するだけです。
僕はProgram
というクラスを作成して、このクラスを起点にサブクラスを作成することにしました。
このように起点となるクラスを作成すればインターフェースは自ずと決まってきます。
Dispatch
から呼び出すクラスはProgram
のサブクラスなので、できる限り処理をProgram
クラスに記述します。
それによってサブクラスの負荷を下げるように設計します。(継承
は開発者を楽にして、開発効率を上げた上に保守性も上げるという素晴らしい仕組みです。ぜひ活用していきましょう!)
このような形で主要処理が出来上がりました。
※こんなバグも出してしまいましたが・・・。(恥
肉付けをしていく
最後は肉付けです。
初期の構想時点から値段をつけるファイルはrb
以外にもcoffee
やscss
などというファイルが存在していることはわかってました。
なので、それらの部分をこのように肉付けしていきます。
若干ダルい部分ですが、このようなプログラムを書くケースは往々にしてあると思います。
この時点で係数にもいろいろ試行錯誤したことがわかると思います。
命名に関して
・・・。すいません。
本来ならこの部分は重要になるのですが、小さいプログラムだったため結構手を抜いてしまいました・・・。
命名に関してはあまり参考にしないでください・・・
大きなプロジェクトでは、命名にはそれなりに時間をかける必要があります。
後で見た時にこのプログラムが何をしているかがわかる名前をつけておくと肉付け
をする時や改修
をする時に楽になります。
ピックアップすべき技術
・・・という程のプルグラムは書いてないのですが、微細ながら書いておきます。
絶対パスの作成
自分のファイルを起点に絶対パスを取得することで、どこに配置されても動くコードができます。
ただし、自分自身の位置を移動させるとプログラムが動かなくなるのでご注意ください。
File.expand_path("../path", __FILE__)
__FILE__
が自分自身の絶対パスを保持しています。
../
としているのは自分自身のパスもディレクトリとして扱ってしまうためです。
../
以下が自分自身のパスになります。
ディレクトリ内部のファイルロード
指定パスの配下に存在するファイル全てを読み込む。
def self.path(path)
Dir[File.expand_path("../../#{path}", __FILE__) << '/*.rb'].each do |file|
require file
end
end
再起ロジックとコールバック
ruby
では&***
と書いた引数はブロックを受け取れます。(&block
の名称が一般的)
そのブロックを使ってコールバックを行ってます。
またread_all_file
内でread_all_file
を呼び出すことで、ディレクトリ内部のディレクトリを再帰的に探し出してます。
昨今では大した技術では無いのですが、昔はこの手の再起プログラムを書ける人は随分少なかったです。
# 再帰的に全てのフォルダを読み込みます。
def read_all_file(current_path = "", &block)
# ディレクトリを読み込みます。
Dir.foreach("#{@rails_path}/#{current_path}") do |file|
# 不要なディレクトリはスキップ
next if file == "." || file == ".."
if FileTest::directory? "#{@rails_path}#{current_path}/#{file}"
# 対象外のフォルダの場合はスキップする。
next if AppConfig.get(:base, :ignore_dir).include?(file)
# ディレクトリの場合は再起する。
read_all_file("#{current_path}/#{file}") do |rails_path, file_name|
# ファイルの場合はコールバックを呼び出す。
block.call("#{rails_path}", file_name)
end
else
# ファイルの場合はコールバックを呼び出す。
block.call("#{current_path}", file)
end
end
end
クラスの返却
Rubyではクラスも含めて全てがオブジェクトです。
なので、以下のようにクラスを返却して取得した先でインスタンスを作成することができます。
今回のケースでは無理に使う必要はなかったのですが、覚えておくと役に立つ時があります。
def hoge
return HogeClass
end
hoge.new
インスタンス変数キャッシュ
以下のプログラムを見ることは多いと思います。
def method_price
@method_price ||= config(:method, :price)
end
@method_price
はここ以外では使ってません。
なんのために行っているかというと、初回呼び出しでは@method_price
にデータを格納しますが2度目からは@method_price
に格納された値を使うテクニックです。
今回のケースではyaml
の読み込みのキャッシュをしているのでほとんど効果は無いのですが、Rails
を使う場合はActiveRecode
を扱う際に役に立ちます。
一度目はSQL
を発行して、2度目からはキャッシュを使うことで高速化&リソースの有効活用ができます。
インスタンスメソッドの動的作成
このテクニックは今回初めて知ったのですが、各サブクラスで一部だけ変更して他の挙動は同じにしたい場合があると思います。
そういう時に以下のような事で動的にメソッドを作る事ができます。
# 使用する設定ファイルの定義
def self.config_file(symbol)
define_method(:config_file) {
@config ||= AppConfig.get(:program, symbol)
}
end
config_file :base
このように記述してサブクラスで以下のように定義するとインスタンス化をする前にメソッドが作成されます。
config_file :ruby
Rails
で有名なところだとbefore_action
なんかがこの手法で作られてます。
インスタンス化した後に値を設定したり、メソッドをオーバーライドしても良かったのですが・・・そうすると管理が面倒になったり行数が増えるためこの方法を採用しました。
結構便利です。
define_method
がミソです。
ちなみにRails
のbefore_action
は以下のように定義されてます。
[:before, :after, :around].each do |callback|
define_method "#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options)
end
end
alias_method :"#{callback}_filter", :"#{callback}_action"
define_method "prepend_#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options.merge(:prepend => true))
end
end
alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
# Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
define_method "skip_#{callback}_action" do |*names|
_insert_callbacks(names) do |name, options|
skip_callback(:process_action, callback, name, options)
end
end
alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
# *_action is the same as append_*_action
alias_method :"append_#{callback}_action", :"#{callback}_action"
alias_method :"append_#{callback}_filter", :"#{callback}_action"
end
まとめ
いかがだったでしょうか?
参考になったかどうかわかりませんが、指針もなくアプリを作るといろいろ迷走をするのでちょっとでもお役に立てればと思います。
作った感想
作ってみるとプログラム規模が小さくて楽しく作れました。
最後の肉付け
は少々面倒でしたが、適当に設定してもそれなりの金額が出るのでまぁいいか
という感じで作ってみました。
僕が今作っているRails
アプリに適用したところそれなりの金額が算出されたので、やってみると面白いと思います。
複数のアプリに対して行うとプログラム規模が比較できるので、一つの指針になるのでは無いでしょうか?
余談
余談ですが、あなたのRailsアプリはいくらは思い立った翌日に開発着手〜完了となりました。
上記の事を約5時間ほどかけて行い、作成に至りました。
筆者について
僕はTownSoftの屋号を掲げている個人事業主です。
お仕事お引き受けしますので、お気軽にご連絡ください。