ブロックについてのまとめ
Rubyを触り始めて早一年が経ちました。
やっと、ブロックつきメソッドを定義し、実装することができました。
そこで、Rubyのブロックに付いて簡単にまとめたいと思います。
参考
- 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2010/03/31
- メディア: 単行本
- 購入: 15人 クリック: 394回
- この商品を含むブログ (79件) を見る
ブロックとは
- 「do 〜 end」や「{}」で囲まれた部分のこと
hoge.fuga do ブロック end hoge.fuga{ブロック}
たとえば、
sum = 0 ary = [1, 2, 3, 4] ary.each{|i| sum += i} p sum # => 10
ブロックつきメソッド
- ブロックを引数としてとるメソッド
- 先ほど例で言うとeachがこれにあたる
- ブロックつきメソッドにブロックを引数として渡して実行することをブロックつき呼び出しという
ブロックつき呼び出し
- ブロックつき呼び出しの一般系
# *args => 引数の並び # block_variables => ブロック変数の並び obj.method(*args) do |block_variables| # ブロック end obj.method(*args){|block_variables| # ブロック }
- メソッドの動作時にブロックが実行される
イテレータ
ブロックつき呼び出しの中で、繰り返しを行うためのイテレータについての説明
配列の場合
sum = 0 ary = [1, 2, 3, 4] ary.each{|i| sum += i}
- 配列「ary」の一要素がブロック変数「i」に代入され、ブロック「sum += i」が評価させる
ハッシュの場合
sum = 0 hash = {:a => 1, :b => 2, :c => 3, :d => 4} hash.each{|k, v| sum += v}
- ハッシュ「hash」のキーとバリューがブロック変数「k」「v」に代入され、ブロック「sum += v」が評価される
ブロックつきメソッドを定義
自分で、ブロックつきメソッドを定義してみる
def hoge yield end def fuga hoge{p "ブロック呼び出し"} end fuga #=> "ブロック呼び出し"
- yieldに呼び出し元で指定したブロックが渡される
- つまり、hogeがブロックつきメソッド
ブロック引数がある場合のメソッド
def hoge yield "ブロック呼び出し" end def fuga hoge{|str| p str} end fuga #=> "ブロック呼び出し"
- yieldの引数が、ブロック変数へ代入され、ブロックが実行される
使用例
あるメソッドを定義したけど、そのメソッド内の処理を呼び出し元に依存させたい時にブロックを使いました。
使いどころは、まだまだ分かっていませんが、使えるとかなり便利だと感じました。
次はこれで勉強します。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 13人 クリック: 220回
- この商品を含むブログ (66件) を見る
それでは、Proc.newとかlamdaとかprocに関してはまた今度。
最後までお読みいただきありがとうございました。
ページネーションでハマったと思ったら、findの問題だった
開発をしていて、ページネーションでハマったと思って、原因を探してみたら、findの仕様だったということがあったのでメモ
環境
- rails1.2.3
データ構造
- リレーション
class User < ActiveRecord::Base has_many :diaries end class Diary < ActiveRecord::Base; belongs_to :user end
事象
やりたかったこと
- 一度でも日記を書いたことのあるユーザーを日記をincludeして取得する
作戦
- INNER JOINを利用して、diariesを持っていないユーザーを取得しないでおく
@pages, @users = paginate( :user, :joins => "INNER JOIN diaries dummy ON dummy.user_id = users.id", :include => :diaries, :per_page => 5 ) # @pages.item_count #=> 15 # @users.size #=> 3
意図したとおりにレコードが取得できるはず!と思っていたのですが、なぜか、@users.sizeの値がper_pageに満たない。
paginateメソッドを追ってみる
rails1.2.3なので、ActionControllerにPaginationが標準実装されている
結局、paginateメソッドは以下のことをやっていだだけだった
# File actionpack/lib/action_controller/pagination.rb, line 182 182: def find_collection_for_pagination(model, options, paginator) 183: model.find(:all, :conditions => option[:conditions], 184: :order => options[:order_by] || options[:order], 185: :joins => options[:join] || options[:joins], :include => options[:include], 186: :select => options[:select], :limit => options[:per_page], 187: :offset => paginator.current.offset) 188: end
findを呼び出している以外に特別なことは何もしていない。
つまり、今回の事象は、findの仕様によるものだったのだ。
findメソッドを追ってみる
以下、重要なメソッドだけを記載する
# File activerecord/lib/active_record/base.rb, line 411 411: def find(*args) 412: options = extract_options_from_args!(args) 413: validate_find_options(options) 414: set_readonly_option!(options) 415: 416: case args.first 417: when :first then find_initial(options) 418: when :all then find_every(options) 419: else find_from_ids(args, options) 420: end 421: end
今回、paginateメソッドでは、find(:all)が実行されているので、find_everyメソッドをみる
# File activerecord/lib/active_record/base.rb, line 994 994: def find_every(options) 995: records = scoped?(:find, :include) || options[:include] ? 996: find_with_associations(options) : 997: find_by_sql(construct_finder_sql(options)) 998: 999: records.each { |record| record.readonly! } if options[:readonly] 1000: 1001: records 1002: end
scopeはかかっていないが、includeは指定しているので、find_with_associationsメソッドをみる
# File activerecord/lib/active_record/associations.rb, line 1020 1020: def find_with_associations(options = {}) 1021: catch :invalid_query do 1022: join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) 1023: rows = select_all_rows(options, join_dependency) 1024: return join_dependency.instantiate(rows) 1025: end 1026: [] 1027: end
JoinDependencyのインスタンスであるjoin_dependencyでJOIN句に入れるべきものを制御している
そして、select_all_rowsメソッドの中で、construct_finder_sql_with_included_associationsが呼び出されている
このメソッドで、DBへ投げるSQLを生成している
以前紹介したjoins オプションとinclude オプションの決定的な違い - mic_footprintsのincludeの処理もこの当たりで対応している
- column_aliases(join_dependency)でエイリアスを貼っている
# File activerecord/lib/active_record/associations.rb, line 1172 1172: def construct_finder_sql_with_included_associations(options, join_dependency) 1173: scope = scope(:find) 1174: sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} " 1175: sql << join_dependency.join_associations.collect{|join| join.association_join }.join 1176: 1177: add_joins!(sql, options, scope) 1178: add_conditions!(sql, options[:conditions], scope) 1179: add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) 1180: 1181: sql << "GROUP BY #{options[:group]} " if options[:group] 1182: 1183: add_order!(sql, options[:order], scope) 1184: add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) 1185: add_lock!(sql, options, scope) 1186: 1187: return sanitize_sql(sql) 1188: end
paginateメソッドが生成したlimitオプションがあるので、add_limited_ids_condition!が実行され、一度、該当するidが取得される
その取得されたidを基に、アソシエーションするべくクエリが生成される
結論から言うと、この取得するidに問題があった
今回のハマりポイントに到着!!
# File activerecord/lib/active_record/associations.rb, line 1172 1190: def add_limited_ids_condition!(sql, options, join_dependency) 1191: unless (id_list = select_limited_ids_list(options, join_dependency)).empty? 1192: sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) " 1193: else 1194: throw :invalid_query 1195: end 1196: end
結局、問題は上記メソッド内で実行されるselect_limited_ids_listメソッドにあった!
- 正確に言うと、もっと奥のメソッドだけど
add_limited_ids_condition!自体は、select_limited_ids_listメソッドの返り値(idの配列)を元にSQL文を生成しているに過ぎない
select_limited_ids_listメソッド内で以下が実行される
# File activerecord/lib/active_record/associations.rb, line 1205 1205: def construct_finder_sql_for_association_limiting(options, join_dependency) 1206: scope = scope(:find) 1207: is_distinct = include_eager_conditions?(options) || include_eager_order?(options) 1208: sql = "SELECT " 1209: if is_distinct 1210: sql << connection.distinct("#{table_name}.#{primary_key}", options[:order]) 1211: else 1212: sql << primary_key 1213: end 1214: sql << " FROM #{table_name} " 1215: 1216: if is_distinct 1217: sql << join_dependency.join_associations.collect(&:association_join).join 1218: add_joins!(sql, options, scope) 1219: end 1220: 1221: add_conditions!(sql, options[:conditions], scope) 1222: if options[:order] 1223: if is_distinct 1224: connection.add_order_by_for_association_limiting!(sql, options) 1225: else 1226: sql << "ORDER BY #{options[:order]}" 1227: end 1228: end 1229: add_limit!(sql, options, scope) 1230: return sanitize_sql(sql) 1231: end
注目は、1216行目のif文
これがあることで、今回の問題が生じた
1217, 1218で、JOIN句にjoinやincludeで指定したものを入れてくれている
が、is_distinct #=> falseなので、if内は回避される
include_eager_conditions?(options) # WHEREに指定されるtableがJOIN句への挿入を必要としているか? include_eager_order?(options) # ORDER BYに指定されるtableがJOIN句への挿入を必要としているか?
今回の場合、どちらもfalseである
というわけで、JOIN句には何も挿入されないまま、idが取得される
そのあとで、idを元に、INNER JIONが指定されるので、結果としてlimitよりも少ない数が取得されてしまうことがある
今回の場合だと、以下の方法で実現が可能
@pages, @users = paginate( :user, :include => :diaries, :conditions => "diaries.id IS NOT NULL", :per_page => 5 )
この問題って、2.系以降だと変更されていそうな気もします。まだ追っていませんが。
というか、そもそも問題なのか。。。ご意見あれば是非!
最後までお読みいただきありがとうございます。
クラスとインスタンスのまとめ
Rubyをはじめたころの自分の記事が意外にも分かりやすかったので、改変して再掲載しますw
クラスメソッドとインスタンスメソッド - mic_footprints
☆クラスメソッド
クラスとは
- クラスとは、オブジェクトの種類を表したもの
- つまり、複数のオブジェクト(インスタンス)の枠組みと捕らえてよいでしょう
クラスメソッドとは
- クラスに直接働きかけるメソッドのこと
- 例
Student.find(1)
-
- findメソッドはStudentクラスに直接働きかけている
- つまり、クラスメソッド
学級と生徒で例えてみる
- 状況
- C組という学級(クラス)がある
- C組には太郎君、花子さんがいる
- つまり、太郎君、花子さんは、C組クラスのインスタンス(C組オブジェクト)
- C組の太郎君を探すメソッドを呼び出す
C.sagasu("太郎君")
-
- ここでのsagasuメソッドは、クラスメソッド
- sagasuメソッドはC組に直接働きかけている
たとえば、C組の太郎君を廊下に立たせるためのメソッドというのはクラスメソッドではありません
これは、次で説明するインスタンスメソッドです
☆インスタンスメソッド
☆メソッドとレシーバ
- メソッド(method)が働きかける先をレシーバ(receiver)という
メソッド
- オブジェクトに関連する操作を行う
- Rubyでは、全ての操作がメソッドとして実装されている
メソッドの呼び出し
オブジェクト.メソッド名(第一引数, 第二引数, 第三引数, ・・・, 第N引数)
- メソッドを実行するということは、「オブジェクトにメッセージを送る」ということ
- 引数がある場合は、引数と共にメッセージが送られるというイメージ
レシーバ
- メソッドから操作されるもの
- つまり、「オブジェクトはメッセージを受け取る(receive)」ということ
クラスメソッドとインスタンスメソッドに於いて
個人的な感想ですが、レシーバを意識することで、オブジェクト指向の感覚がつかめた気がします。
プログラミングをはじめたばかりの方々に少しでも参考になれば幸いです。
- 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2010/03/31
- メディア: 単行本
- 購入: 15人 クリック: 394回
- この商品を含むブログ (79件) を見る
最後まで、お読み頂きありがとうございました!
SQL改善によるパフォーマンス向上
事例
実際にアプリケーション開発を行っていて、検索機能を実装していたときに超絶重い検索ロジックに出会いました。
その対処をSQL文の改善で行なったので紹介します。
データ構造
- 環境
- MySQL 4.1
mysql> DESCRIBE users; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | | PRI | NULL | auto_increment | | name | varchar(40) | YES | | | | +-------+-------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) mysql> DESCRIBE diaries; +------------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+----------+------+-----+---------+----------------+ | id | int(11) | | PRI | NULL | auto_increment | | user_id | int(11) | | MUL | 0 | | | body | text | YES | | | | | created_at | datetime | YES | | | | +------------+----------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
重いクエリ
- ユーザーにとって一番最近書いた日記で、内容に「最新」が含まれる日記を書いたユーザーを取得する
SELECT users.* FROM users LEFT OUTER JOIN diaries ON diaries.user_id = users.id WHERE users.id IN ( SELECT diaries.user_id FROM ( SELECT MAX(diaries.created_at) AS created_at, diaries.user_id FROM diaries GROUP BY diaries.user_id ) recent_diaries, diaries WHERE diaries.user_id = recent_diaries.user_id AND diaries.created_at = recent_diaries.created_at AND diaries.body LIKE '%最新%' );
やりたかった事
- ユーザーにとっての最新の日記群(recent_diaries)を作る
- そのrecent_diariesのuser_idとcreated_atが一致するdiariesのbodyに「最新」が含まれているdiariesのuser_idを取得する
- recent_diariesはuser_idでGROUP BYしているので、意図したdiariesの情報が取得できない
- この場合は、bodyの情報
- 意味があるデータとして取得できるのは、user_idと最新のcreated_atのセットのみになる
- ユーザーにとっての最新の日記を取得するためには、recent_diaries.user_idとrecent_diaries.created_atがdiaries.user_idとdiaries.created_atとが一致しているかを見ることによってのみ実現する
- さらに、そのユーザーにとっての最新の日記のbodyに「最新」が含まれているかを見ることによって、意図した日記群が取得することが出来る
- recent_diariesはuser_idでGROUP BYしているので、意図したdiariesの情報が取得できない
- 取得したuser_id群にusers.idが含まれているusersを取得する
- 意図した日記群のuser_idをSELECTし、そのuser_id群にusers.idが含まれているかを見ることによって意図したユーザーを取得する
原因
- 相関サブクエリを使用している
相関サブクエリとは( DEPENDENT SUBQUERY )
- サブクエリにおいて外部クエリのカラムを参照しているサブクエリのこと
- つまり、外部クエリのカラムの値と相関関係を持っているサブクエリのこと
- 例えば、
SELECT users.id FROM users WHERE users.id = ( SELECT diaries.user_id FROM diaries WHERE diaries.user_id = users.id AND diaries.body LIKE "%相関%" GROUP BY diaries.user_id );
※相関サブクエリを使わなくても意図したレコードは取得できますが、あくまでも例としてあげています。
- 外部クエリusers.id = ()に応じて、()内のDEPENDENT SUBQUERYが実行される
- 基本的に、相関サブクエリは重いとされているが、インデックスを貼ることで、速度は大幅に改善される
- この場合、diaries.user_idにindexを貼ることで速度改善が見込める
相関サブクエリはどこ?
- 一見、問題のクエリに相関性は内容に見える
- EXPLAINで出力
+----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+---------------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+---------------+-------+-------------+ | 1 | PRIMARY | users | ALL | NULL | NULL | NULL | NULL | 23912 | Using where | | 1 | PRIMARY | diaries | ref | index_diaries_on_user_id,user_id | user_id | 4 | test.users.id | 1 | | | 2 | DEPENDENT SUBQUERY | <derived3> | ALL | NULL | NULL | NULL | NULL | 3455 | | | 2 | DEPENDENT SUBQUERY | diaries | ref | index_diaries_on_created_at,index_diaries_on_user_id,user_id | index_diaries_on_user_id | 4 | func | 1 | Using where | | 3 | DERIVED | diaries | index | NULL | index_diaries_on_user_id | 4 | NULL | 45561 | Using where | +----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+---------------+-------+-------------+ 5 rows in set (2.95 sec)
- 3行目と4行目、「DEPENDENT SUBQUERY」との記載がある
- つまり、相関サブクエリが存在することなっている
- ユーザーの気持ちとしては、users.id IN (DEPENDENT SUBQUERY)を以下の通りに処理してほしいはず
- ()内をusers.idとの相関性を持たせるのではなく、()内のSUBQUERYを実行した結果を元にusers.id IN ()を評価する
- しかし、実際はusers.idと相関を持ち、()内が評価されてしまう
現時点でのMySQL(バージョン5.1)では、サブクエリはまず外部クエリの条件から評価される。そして、外部クエリの条件に合致する行が見つかると、その行がサブクエリの条件に合致するかどうかが評価されるわけである。即ち、サブクエリにおいてフェッチしなければいけない行数が平均N行、外部クエリでフェッチされる行数がM行のとき、サブクエリにおいてM×N行の評価が行われることになる。これは膨大な計算量である。
参照:漢(オトコ)のコンピュータ道: なぜMySQLのサブクエリは遅いのか。
- つまり、usersの数だけ()内のSUBQUERYが走ることになる
- この場合、()内の処理単体で0.3秒かかっていた
- 0.3sec * 23912 = 119.56min
- つまり、単純計算で、最低でも2時間はかかるクエリとなってしまっていたのだ!
改善
先に、ユーザーにとっての最新の日記群を作ってしまう
- usersの数だけ、最新の日記群を作るクエリが走ることが問題であれば、先にその日記群を作ってしまえば、毎回最新の日記群を作らなくて済むはず
SELECT users.* FROM users, ( SELECT MAX(diaries.created_at) AS created_at, diaries.user_id FROM diaries WHERE diaries.event_name NOT LIKE '%選考' AND diaries.user_impression IS NOT NULL GROUP BY diaries.user_id ) recent_diaries LEFT OUTER JOIN diaries ON diaries.user_id = users.id WHERE users.id IN ( SELECT diaries.user_id FROM diaries WHERE diaries.created_at = latest_records.created_at AND diaries.user_id = latest_records.user_id AND diaries.body LIKE '%最新%' );
- こうすることで、最新の日記群を作るクエリをusersの数だけ走らせない様にすることは実現できた
- しかし、次の問題が生じた
- EXPALINで見てみよう
+----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+------------------------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+------------------------+-------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 3455 | | | 1 | PRIMARY | users | ALL | NULL | NULL | NULL | NULL | 23912 | Using where | | 1 | PRIMARY | diaries | ref | index_diaries_on_user_id,user_id | user_id | 4 | test.users.id | 1 | Using index | | 3 | DEPENDENT SUBQUERY | diaries | ref | index_diaries_on_created_at,index_diaries_on_user_id,user_id | index_diaries_on_user_id | 4 | recent_diaries.user_id | 1 | Using where | | 2 | DERIVED | diaries | index | NULL | index_diaries_on_user_id | 4 | NULL | 45561 | Using where | +----+--------------------+------------+-------+--------------------------------------------------------------+--------------------------+---------+------------------------+-------+-------------+ 5 rows in set (0.26 sec)
- 一行目のテーブル
は、id=2のDERIVED(5行目)に由来するテーブルであることを示している - つまり、FROM句のサブクエリの結果のテーブル「recent_diaries」のことである
- このようなDERIVEDに由来するようなテーブルを「テンポラリーテーブル」と呼ぶ
- テンポラリーテーブルrecent_diariesのkeyはNULLである
- つまり、indexが使えないのである!
- テンポラリーテーブルはフルテーブルスキャンが走る!!
- ・・・テンポラリーテーブルのレコード数が多いと、爆弾クエリになってしまうorz
別の切り口を考えてみる
- 作戦
- そもそも、ユーザーにとっての最新の日記は一つである
- 相関サブクエリを利用して、ユーザーごとの最新の日記という条件を加える
- さらに、日記のbodyに「最新」が含まれているという条件を加える
- 上記の条件に当てはまった日記を持つ学生を取得する
SELECT users.* FROM users LEFT OUTER JOIN diaries ON diaries.user_id = users.id WHERE diaries.id = ( SELECT diaries.id FROM diaries WHERE diaries.user_id = users.id ORDER BY diaries.created_at DESC LIMIT 1 ) AND diaries.body LIKE '%最新%';
- うん!シンプルかつ高速なクエリができました!
まとめ
SQLを組むときは以下のことに注意したい
- 意図しない相関サブクエリが生成されていないか?
- 正確な場所にインデックスが貼られているか?
- テンポラリーテーブルが巨大になっていないか?
- 作者: 小野哲
- 出版社/メーカー: 技術評論社
- 発売日: 2007/12/25
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 318回
- この商品を含むブログ (17件) を見る
以上です。
最後までお読みいただきありがとうございました。
開発のチーム体制についてまとめてみた
Wed+DB PRESS Vol.60でプロジェクト運用ノウハウについて学んだのでまとめました。
アウトプットを最大化するチームづくり
- チームラボ(株)
チーム開発の重要性
- 求められるアウトプットに対して最適なチームを運営する必要がある
- チームメンバー個人個人がサービスの目的や、最適な開発フローを常に考えてることが重要
- 一人一人のアウトプットの結晶が、プロジェクト全体のアウトプットにつながる
- 個人個人のアウトプットを担保するのがチーム
- チームとして品質を向上させることが必要
レビューの重要性
- プロジェクトの方向性を疑うことを忘れないのが大切
- プロジェクト内のみにとどまらない
- プロジェクト間においても横断的に、かつ定期的にレビューを行う
- レビューを活性化するために
- プロジェクトに直接コミットしていない、異なるスペシャリストもレビューに含める
- レビューはフラットな関係性で行う
チーム分けのパターン
- スキルの高いメンバーだけで構成されている場合
- 機能でチームを分ける
- 各担当者が1つの機能の開発を担当
- 設計から動作確認まで自分で全て行う
- 開発の楽しさを感じられるチーム編成
チーム分けの際に同時に考えること
チームリーダー
- チームリーダーはメンバの、最高のパフォーマンスと最高のアウトプットを担保する必要がある
- ただし、その目的はプロジェクト全体のアウトプットに向けられているべきで、決してチーム内のアウトプットを担保するものではない
- チームリーダーは、チーム内で技術的にもっとも優れている必要はない
- チーム内で最もアウトプットに対して求めるクオリティの意識が高い人である
- 全体のアウトプットの進捗や品質を意識しながら、自分のチームへとブレークダウン
- 他のチームへ正確な情報を伝達することも求められる
- チーム内の進捗の把握と共有は、リーダーが管理するためのものではなく、プロジェクト全体のアウトプットを最高のものにするために、正しい情報をリーダーたちと共有するために行うべき
- プロジェクトのアウトプットを考えて、自分のチームが苦戦を強いられているときは即座にアラートを出す
アジャイルな見積りと計画づくり
- (株)永和システムマネジメント
受託開発の難しさ
- プロダクトは「百聞は一見にしかず」
- プロジェクトは「一期一会」
見積もり
- 近いものほど細かく、遠そうなものほど粗く見積もる
- それぞれを相対的な大きさで見積もる
- 見積もりはなるべく開発チームメンバー全員で行う
完了の定義
- 完了の定義をお客様と開発者で合わせておくことが重要
- 完了の定義をフィーチャ(プロダクトで実現したいことをユーザー視点で記述したもの)ごと、イテレーションごと、プロジェクトごとに設定することも重要
アジャイルUXDによる"試行錯誤"のプロセス化
- ゼロベース(株)
UXD
- ユーザーエクスペリエンスデザイン
設計と失敗
- 作る前に考えすぎることなく、まず現物を作ってから考えること
- なるべく早く、少しずつ、実際に動くものを作ること
- 実験して「失敗」しても「学習」と捉える
- 取り返しの付く小失敗は学習のために不可欠
- その学習を短期間に何度も繰り返す
- 「分からない」とこは「分からない」と認めることが重要
- あらゆる願望は現実の前に無力
- 現実に立脚することでしか、失敗を避けられない
- 取り返しのつかない大失敗は、それが失敗だと気がつかずにどんどん進んでしまったときに発生する
不都合を生じる可能性があるものは、いずれ必ず不都合を生じる
アジャイルUXDのプロセス
- 戦略
- 何をしようとしているのか、利用者にどんな体験を提供しようとしているのかを共有
- スコープ
- スコープ(要件リスト)を検討し、決定
- 各メンバーのパフォーマンスを最大化するようなタスクの割り振りを行う
- 構造、骨格
- 要件を実際に設計する際には、まず構造レベルでUIフローを考る
- その後骨格レベルの「HTMLモック」を組む
- 表層
- HTMLモックをもとに静的HTMLのテンプレートを作成
- テストとリリース
信頼とコミュニケーション
- 新規事業の不確実なことが多いので、「やってみなければわからない」ことが多い
- 細かい修正をやり直している暇があったら、そのままリリース
- 利用者の反応を見ながら、別の要件を開発したほうがよい
- このことにより、チーム全体のパフォーマンスを上げる
タスクベースの計画
- 「何をすれば新規事業が成功するか」ということが事前に分かっていることはない
- つまり、事前に計画しすぎないことが大切
- これにより、急な戦略変更/要件変更にも柔軟に対応できる
- 戦略とスコープを押さえた上で、直近のタスクの優先順位だけをしっかり決める
- チームメンバーは最も重要な作業から一つづつ処理していく
- 要点は、チーム全体のパフォーマンスを最大化すること
ソーシャルゲームの創り方・育て方
考えた人が作る
- スピードを第一に考えると、「どんなものを作るか」について頭の中にイメージがある人がそのままコーディングして実現するのが理想的
- エンジニア自身がデータ分析に関わり、ユーザーのニーズやユーザビリティを理解し、仕様を決める過程に多く関わるようにする
シンプルかつ少数精鋭でユーザーインパクトの大きいものを
- システムはできるだけシンプルに
- 少人数でも開発・運用できるものに
- ユーザーへのインパクトはできるだけ大きなものに
- できるだけ多くのユーザーに喜んでもらえるものに
前例がないものを作る
- 前例がないものを作るときはやってみないとわからない
- 手っ取り早く動くものを作って、自らそれを使ってみる
- 動かしてみたら、違う側面も見えてくる
- そこでプログラミングしなおす
- 動かして、修正するといういわば「粘土をこねる」ような作業を繰り返す
- 誰も見たことのないサービスやゲームが生まれる
- つまり、サービスの企画者が実装も行えると、良いものを考えながら作ることができる
- サービスの実現性が技術と表裏一体になっていて、出来れば、一人の人間が両方の側面を持っているのが理想的
サービスは「創る」だけではなく「育てる」もの
- 開発のゴールはリリースではない
- リリースしたあと、どれだけサービスとして成長させられるかが勝負
- ユーザーの行動をDBやアクセスログなどからつかみ、改善点を見つけて改善していく
- 状況を把握し、次の打つ手を考え、開発/投入し、効果を分析して次にまた活かすというサイクルを、できるだけ少人数で素早く回していく
- 考える人とコードをいじる人を分割しないことが重要
インフラチームと開発チーム
- インフラチーム
- サーバの運用/保守、システム性能監視を担当し、障害発生時の一時切り分けなどを担当
- それに加えて、データベースのテーブル設計のレビューやSQLチューニングでも貢献
- 開発チーム
- このような体制を敷くことによって
- インフラチームが常時性能監視を実施し、適切な対応方針を定めることができるため、急激なユーザーの増加や新規機能追加時のシステム負荷増加に即時対応出来る
- そのためには、インフラチームの高いシステム運用力が必要
大きめのチームによる開発サイクル
- 案件出し
- 案件計画
- 2週間に一度、登録された案件を確認し、優先順位をつけて、スケジュール概要と開発担当を決める
- 日々の作業
- 業務終了時に現時点での作業状況をチケットに記載する
- 朝会で前日にアップデートされた案件リストを見ながら各メンバーが作業報告を行う
- 進捗が計画より遅れている場合は、他のメンバーに手伝ってもらうように依頼する
- 逆に前倒しで進んでいる場合は「このチケットも吸収できそう」というような提案を行う
- 前日の実装を通して見えてきた仕様に関する議論を行うこともある
- 朝会は、「今」に焦点を合わせたMTG
- 週次MTG
- 長期的なスパンの話題をする場
- 分析の結果やアイデア出しなど
- 案件レビュー
- イベントがひとつ終わるごとに関係したメンバーで振り返りを行う
- 売上やユーザー行動の分析を行い、次の施策に結びつける
- 障害が発生した場合は、再発防止の検討やプロセス自体の改善について議論する
情報共有・記録
- チーム間での情報共有がおろそかになりがちなので、情報共有の場の提供を意識する
- エンジニア全員が集まる定例会議を実施
- 各ゲーム開発の進捗状況を共有するとともに、発生した障害の共有、技術/運用ノウハウの共有を行う
- Wikiには、技術情報、運用情報など記録
- やるべきことのチェックリストなどを記録化し、作業の抜け漏れを防ぐ
ブックをCSVファイルに変換するマクロを書いてみた
友人から以下のような依頼が来たのでVBAを書いてみました
- 参照ライブラリ
- Microsoft Scripting Runtime
- コード
Sub ChangeBookToCsv() Application.DisplayAlerts = False Dim Path As String Dim OutputPath As String Dim FSO As New FileSystemObject Dim TargetFile As String Dim CsvFile As String Path = "" ' Bookが保存されているフォルダのパス OutputPath = "" ' CSVファイルを保存したいフォルダのパス TargetFile = Dir(Path & "\*.xls*") Do While TargetFile <> "" CsvFile = Split(TargetFile, ".")(0) & ".csv" Workbooks.Open (Path & "\" & TargetFile) Sheets(1).Select ActiveWorkbook.SaveAs Filename:=Path & "\" & CsvFile, FileFormat:=xlCSV ActiveWindow.Close TargetFile = Dir() Loop End Sub
もっと早い効率的な方法があれば共有していただけると嬉しいです。
最後までお読みいただきありがとうございました。
EXCEL HACKSが教えてくれたこと
Excelとは
Excelは多くの業種に於いてビジネスを変革する基板となり、
世界中で意思決定のサポートをしている超強力な表計算ソフトです。
ちなみに、ExcelはMS社製ということもありWindowsのイメージが強いですが、初代ExcelはMac用のソフトでした。
今回の参考資料
Excel Hacks 第2版― プロが教える究極のテクニック140選
- 作者: David Hawley,Raina Hawley,羽山博,日向あおい
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/11/22
- メディア: 単行本(ソフトカバー)
- 購入: 9人 クリック: 46回
- この商品を含むブログ (15件) を見る
VBAとか言う前にやっぱりExcel
少しExcelが出来るとマクロとかに興味が出てくるものですが、
私は、Excel自体の機能についての理解をもっと深めることをおすすめしたいです。
というのも、Excel自体が超強力なソフトだからです。
それでは、そのExcelを最大限に利用する方法や、考え方などをまとめていきたいと思います。
表を作る前に
ある情報をまとめて表にしておきたいと思った際の注意点
- 長期的な視点に立つ
- データや数式の追加を常に念頭におく
- 追加や変更は極めて頻繁に発生するのはご承知のとおりです
表とは
- 表というのはユーザが正しい情報を得るためのものである
- 情報をその場限りで見栄え良く表示出来ればいいというものではない
これらを踏まえた上で
- 表の作成時間の8割を設計に割き、残りの2割を設計を元に具象化する作業にあてる
- この方法は、短期的には非効率だが、長期的では十分にペイする非効率である
これ似た考え方がプログラマの世界にもあります。
スキルのレベルにかかわらず、プログラマーは全時間のおよそ10〜20%をコードを書くのにあてており、
たいていのプログラマーは完成品ができるまで一日あたりおよそ10〜12行のコードを書いています。
優秀なプログラマーは残りの90%のうち大部分を、考えること・調べること・最高の設計を見つけるための検証作業に費やします。
参照:Loading...
そう、知識労働に於いて体を動かすことよりも頭を動かすほうがはるかに重要なのです。
表のデータ配置
実は、Excelにはデータの配置にルールが存在します。
このルールを守らないと一部の機能が正しく動作しません。
表構成における悪い例
- 必要以上に多くのブックにデータを分散させる
- 必要以上に多くのワークシートにデータを分散させる
- 必要以上に多くの表にデータを分散させる
- 表の行列に空行を挿入する
- 上や左のセルと同じ値のセルを空白のままにしておく
これらの基本的なルールを守らないとピポットテーブルなどの強力な機能の利用が制限される可能性があります。
特に、はじめの3点に関しては、「関連するデータは1つの連続した表の中に収める」ということで対応できますし、
これを怠ることで、Excelの機能が制限されるばかりでなくデータ不整合が起きて業務に致命的なダメージを与えるかも知れません。
ルール
- 表の1行目にはそれぞれの列に対応する見出しを記述
- 見出しのすぐ下からデータを配置する
- それぞれの列の中で同じデータが繰り返されていても空欄を利用して繰り返しを表現しない
- 可能なかぎりデータは並び替えておく
データの並び替えに関しては、検索や参照といった機能を利用するときに、
並び替えられていないデータでは、処理ができないことや、時間が大幅にかかってしまうということがあるからです。
また、これらを見てみるとExcelで列数<行数となる理由が分かってくると思います。
表内の書式
書式設定はトラブルの元
書籍設定を使って表を読みやすくし、理解しやすくすることは大切です。
ただ、この書式設定を使用することで処理の効率が落ちてしまっては意味がありません。
書式設定に時間を費やせば費やすほど効率性は低下してしまいます。
しかも、書式設定をすればするほどファイルサイズが増大し、挙動が重くなったりします。
セルの結合は問題の元
セルを結合する意味ってなんでしょうか?
一度考えてみてください。
これが、見た目を保つためであるのであれば、即刻利用をやめたほうがいいと思います。
※見出しは別です。
というのも、表のデータを処理する際にセルが結合していることで様々な困難に直面るからです。
でも、どのようにして見た目を保てばよいのでしょうか?
[書式(O)]→[セル(E)...]
[セルの書式設定]ダイアログボックスの[配置]タブ
[横位置]のリストから[選択範囲内で中央]を指定する
このような方法があるので、こちらに移行してみてください。
(Excel2003)
表内の数式
効率の悪い参照
A1〜H1000までのセル範囲からなる表があり、VLOOKUP関数等の検索関数を使用する場合に、
参照範囲をA1:H65536などに指定していませんか?またはA:H
(65536行はExcel2003の最大行数)
もちろん、表のレコード数(行数)が増加することは想定されます。
しかし、あまりにも効率が悪く、多用すると挙動が極めて遅くなることが想定されます。
では、どのようにすれば良いのでしょうか?
可変長の名前付きセル範囲を利用すればいいと思います。
新しいデータが追加される度に参照先のセル範囲を変更する必要がなくなり、また、効率的に参照が行われるので、便利です。