子育てエンジニアとして成長するために2015年を振り返ってみる
「MoneyForward Advent Calendar 2015 - Qiitaの19日目」の記事です。
子育てとエンジニアという視点で今年を振り返ってみたいと思います。
今年は占いによると僕は良い年らいしいのですが、気づけばもう終わろうとしている。。何が良かったのか、マネーフォワードに JOIN できたことか、ひとまず今年の出来事が来年以降良い方向に繋がってくれることを期待。
簡単に自己紹介。
僕は今27歳でエンジニアとして働く一方で、妻と5歳の長男と2歳の次男がいます。
学生結婚なので社会人年数よりも子育て年数の方が長いです。
結婚して子供ができるとコードを書く時間が減るとよく言われます。ただ、子供のいない社会人生活を知らないので、そこの変化にどう対応するかとかは分からないですが、子供がいる中でどう家族、子育て、仕事のバランスを保つようにしているか今年振り返りながら書いてみたいと思います。
家族と仕事
今年1年間は妻が製菓学校に通っていて、土日は製菓の会社で働いていたため休みの日は僕が子育てしていました。
子育てはいつ何が起こるか予測できません。喚くこともあれば、突然体調崩したりすることもあります。仕事のようにスケジュール立てることはできないに等しいので、正直仕事よりも大変な部分は多いです。主婦の大変さがよく分かりました。。
しかも製菓って大変なんですね。朝早いし、休み少ないし、そんなん知らずに 学校? 行っていんじゃない、製菓の仕事? やりたいならやっていんじゃない、と軽く答えてしまった僕の責任。ちゃんと調べとけばよかったw
妻が朝早いので僕が長男を幼稚園、次男を保育園に送ってから出社しているので朝は戦いですね。
ただ、家族がやりたいことできてたほうが良いしこれまでの経験上家庭で問題起こると仕事もうまくいかないですね。家庭がうまくまわれば必然的に仕事もうまくまわります。なので結果的には正解だったなと思います。
これまですべてにおいて仕事第一で考えていた時期もあったのですが、結果的に家庭うまくまわらず仕事にも影響でるケースもあったので、基本家庭第一で考えたほうが楽だなと感じた年でした。
家族とエンジニア
仕事からエンジニアという視点で考えた時、エンジニアって家庭とのバランス取りやすい職種だなと感じてます。
割と柔軟な働き方ができるので、子供のイベントには結構参加できます。幼稚園の運動会のリレーとか積極的に走ります。長男の遠足に一緒に行った際も周りはお母さんでお父さん僕一人の状況もありました。
そうなると、まわりのお母さんが妻に対し、旦那さんえらいよねー と言ってくれるので妻としては悪い気しないですよね。
父親の立場としてもメリットしかないですね。まぁ始めからメリットを考えて動いたわけではなく結果的にそうなったと。
エンジニアという職種だからできた部分でもあったなと感じました。
子供のためにと思って参加したことで、それが家族にとって良い方向に繋がり、仕事のパフォーマンスにも繋がります。
子育てとプログラミング
エンジニアにとってコードを書くことは重要です。ただ、土日子育てしているとコード書く程のまとまった時間は取れないです。
子育てしながらだといつ何が起こるかわからないので、コード書いていたとしても頻繁に手を止めなきゃです。
今年を振り返ってみてもなかなか書けていないなと。来年は次男が3歳になるのでもう少し子育ても楽になること期待。
ただ長男が最近パソコンに興味持ち始めているのはエンジニアとしては嬉しいこと。
(お母さんがゲームやらせてくれないから)ゲームを作りたい と最近よく言うので早速一緒に何か作ってみようと思います。
小さい頃は何か一緒にやるといってもおもちゃで遊んだりとかがメインでしたが、徐々にやれることが増えて、そしてゲーム作りたいとか言われたら、じゃ作ろうとなりますよね。
来年は子供と色々作りながらコードを書く時間も作っていきたいと思います。
来年の目標
子供たちとプログラミングを楽しむ
プロジェクト内で README を有効に使う
プロジェクトが大きくなるにつれてドキュメントの重要性は増していきます。特にベンチャー企業では、スピード重視でドキュメントを残すよりも開発ばかりに重点が置かれてしまうこともあるかと思います。企画が決まり、じゃ開発、リリースこの日にしようみたいな具合に。とりあえずリリース後に整備しようと思っていても、もはやそんなモチベーションもなく、むしろ次のリリースに向けプロジェクトは進んでいきます。まさに今の自分のプロジェクトがこの状況で、見直さないとなと思っているところです。
ドキュメントなしでの開発
当たり前ですが、開発前の準備がないため開発中に諸々問題が発生します。仕様があいまいなのでここ変更したら別箇所に問題が起こる、開発進んだ段階で新らしく API が必要なことに気づく、など挙げればたくさんあります。開発の手戻りはとにかくコストが高いため、開発前にドキュメントまとめればこういった問題は防げるはずです。と言っても、ドキュメントを書くということもコストかかります。そこで README を有効に使い、そこに概要、デザイン、簡単な仕様、アプリであれば必要な API などまとめておけば、そこまでコストもかからず、その後の開発がかなり効率よく進むのでは思っています。
README の使い方を見直す
去年少し話題にもなった Readme Driven Development。先に README を書いてから開発に入るという手法。そして、その README をドキュメントとしても利用します。そこで機能開発する際は以下のようなテンプレートに沿って README を書こうと考えています。
Name ==== # Overview ## Design ## Description ## API ## Test ## Deploy
これによって、スピード落とさずドキュメントも残せて開発前にある程度の問題は潰せておけるのではと思っています。また人の入れ替わりがあっても引き継ぎがスムーズに進み柔軟な体制が組めます。リリースが完了したら Qiita Team にあげておけば一元管理できるので楽です。
まとめ
Readme Driven Development ベースにまずはこの手法取り入れてみて、プロジェクトにマッチした形にカスタマイズしていきたいなと。README しっかり書くだけでも色々な問題が解決する気がします。
Good By NSLog ~ CocoaLumberjack 2.x.x ~
CocoaLumberJack
iOS には CocoaLumberJack というログライブラリがあります。Java でいう Log4J のようなものです。ログの出力レベルがあり、コンソールやファイルにログを出力できます。もちろんログフォーマットも自由にカスタマイズできます。このライブラリを使うことで、アプリを実機で検証しているときにアプリクラッシュ時のログが追いやすくなり、不具合改修時の手助けになってくれることを個人的には期待しています。
ログ出力レベル
- DDLogLevelError
- DDLogLevelWarning
- DDLogLevelInfo
- DDLogLevelDebug
- DDLogLevelVerbose
- DDLogLevelOff
インストール
pod 'CocoaLumberjack','~> 2.0.1'
設定
import
*-Prefix.pch に書いておくのが楽です。 デバックビルドでは出力レベルを低く設定、リリースビルドでは出力しないようにしています。
#ifdef __OBJC__ #define LOG_LEVEL_DEF ddLogLevel #import <CocoaLumberjack/CocoaLumberjack.h> #ifdef DEBUG static const DDLogLevel ddLogLevel = DDLogLevelVerbose; #else static const DDLogLevel ddLogLevel = DDLogLevelOff; #endif #endif
コンソール出力時のカスタムフォーマット
コンソールに吐き出す際のフォーマットをカスタマイズします。ここでは LogFormatter クラスを新規で作成。
#import <UIKit/UIKit.h> @interface LogFormatter : NSObject <DDLogFormatter> @end
ログレベルと、どのクラスの何行目で吐いているか出力するようにしています。
#import "LogFormatter.h" @implementation LogFormatter - (NSString *)formatLogMessage:(DDLogMessage *)logMessage; { NSString *logLevel; switch (logMessage.flag) { case DDLogFlagError : logLevel = @"error"; break; case DDLogFlagWarning : logLevel = @"warn"; break; case DDLogFlagInfo : logLevel = @"info"; break; case DDLogFlagDebug : logLevel = @"debug"; break; default : logLevel = @"verbose"; break; } NSDateFormatter *format = [[NSDateFormatter alloc] init]; [format setDateFormat:@"yyyy/MM/dd HH:mm:ss"]; NSString *dateTime = [format stringFromDate:[NSDate date]]; NSString *threadID = logMessage.threadID; NSString *fileName = logMessage.fileName; NSInteger lineNumber = logMessage.line; NSString *message = logMessage.message; return [NSString stringWithFormat:@"%@ (%@) %@ [%@(%ld)] %@", dateTime, threadID, logLevel, fileName, lineNumber, message]; } @end
AppDelegate での読み込み
AppDelegate に以下を追加。ここではコンソールとファイルに吐き出すための記述をしています。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Console Log DDTTYLogger *logger = [DDTTYLogger sharedInstance]; logger.logFormatter = [[MfLogFormatter alloc] init]; [DDLog addLogger:logger]; // File Log NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentPathStr = paths[0]; NSString *filePath = [documentPathStr stringByAppendingString:@"/MFLog"]; DDLogFileManagerDefault *fileManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:filePath]; DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:fileManager]; [DDLog addLogger:fileLogger]; return YES; }
ログ出力
適当に以下のようなログを出力します。
DDLogError(@"error log"); DDLogWarn(@"warn log"); DDLogInfo(@"info log"); DDLogDebug(@"debug log");
コンソール
先ほどのカスタムフォーマットが適用されこんな感じになります。どのクラス(SampleViewController)の何行目で吐かれたかもちゃんと表示されています。
2015/07/07 18:39:46 (381577) error [SampleViewController(182)] error log 2015/07/07 18:39:46 (381577) warn [SampleViewController(183)] warn log 2015/07/07 18:39:46 (381577) info [SampleViewController(184)] info log 2015/07/07 18:39:46 (381577) debug [SampleViewController(185)] debug log
ちなみにこれまでの NSLog だとこんな感じです。
2015-07-07 18:43:13.015 SampleProject[34394:385270] error log 2015-07-07 18:43:13.015 SampleProject[34394:385270] warn log 2015-07-07 18:43:13.016 SampleProject[34394:385270] info log 2015-07-07 18:43:13.016 SampleProject[34394:385270] debug log
ファイル
AppDelegate で /Documents 配下を指定しているので、/Documents 配下に log ファイルが配置されます。 今はデフォルト設定でファイル出力していますが、ファイル名や拡張子は好きに変更できます。
まとめ
ログの設定がだいぶ柔軟に行えるようになるかと思います。 ログ周りを整備するだけでもだいぶ開発が捗ります。
アプリケーションフレームワークの選定
今開発しているアプリのリニューアルをするにあたり、サーバサイドのフレームワークの選定が必要となりました。色々考えた結果、Spring boot に決めたのでその経緯をずらずら書いていこうと思います。
なぜリニューアル?
一般的にシステムの寿命は長くて5年と言われています。これはあくまでも一般的であって、iOS や Android アプリでは新しい OS が出たり、新しい端末が発売されたりと、5年はまず持たないですね。今開発している iOS アプリも2年半くらいたつのですが、そろそろ作り替えが必要な時期にきています。ただアプリの作り替えだけでなく、サーバサイドも設計の見直しも必要でした。設計がいまいちイケていなく、アプリで結構ロジックをガリガリ書かなければいけない箇所が多く発生していました。アプリではロジックを減らし表示させることだけに専念し、細かな処理は全てサーバサイドで行うようにできれば、開発スピードも上がり今後の運用も楽になります。ということで、アプリのリニューアルに合わせ、サーバサイドのリニューアルも行うにあたりました。
フレームワークの選定
社内でのサーバサイドは主に Java で書かれており、これまで社内フレームワークを利用し実装していました。しかし、これだけ OSS なフレームワークが充実していてドキュメントも整備されているこの時代に、わざわざ社内フレームワークを使わなくても良いのではというここと、ちょっとした社内事情もありまずはどのフレームワークを利用しようかの選定が必要でした。
Ruby だと Ruby on Rails、Python なら Django、PHP なら CakePHP や Laravel など、これ!というものがありますが、Java だとなにかなーと考えたときぱっと思い浮かぶのはPlay frmework や Spring framework。あとは Dropwizard。
Java であれば個人的には Play framework かなと色々調べてみましたが、Play framework 自体がバージョン2.0 以降、Scala で書き換えられていたり、思い切って Scala で書いても良かったのですが、プロジェクトのメンバーが入れ替わったときの学習コストが高かったりとちょっとうちの会社には合っていないかなと思い断念。(Scala 使いがいないというのと、会社の規模が割と大きいのと、メンバーが流動的に動く環境であったという色々な理由があり学習コストは極力抑えたかった。。)そこで、他の選択肢である、Spring framework と Dropwizard も調べてみたときに、Spring boot なるものがあると知りました。
Spring boot
これは名前からも分かるように Spring プロジェクトの1つです。どうやら Dropwizard の影響を受けているようで、2015年1月時点で最新バージョンは 1.2 で昨年 1.0 がリリースされたばかりです。特徴としては、コードを少量書いてすぐに実行が可能であり、web.xml などの設定ファイルは意識する必要がありません。Maven(gradle も可)の設定のみで OK です。あとはこれまで利用していた社内フレームワークが Spring framework を意識した設計になっていたため、導入への敷居が低かったことも決め手でした。
今リニューアルし始めですが、これまでと比べかなりコード量も減らせ、さくさく開発が進みます。その中で起きた Spring boot に関する Tips も書いていこうと思います。
iOS push 通知からブラウザ起動させる際に注意すること
push 通知からダイレクトにブラウザを起動させたとき、アプリが10秒くらい固まった後にようやくブラウザが起動する問題が起こりました。どうやら、Application Delegate の didReceiveRemoteNotification(※) や didFinishLaunchingWithOptions で [UIApplication openUrl] を呼ぶとアプリが固まってしまうそう。。
※ iOS8 からは didReceiveRemoteNotification ではなく以下に変更されてます。
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)notification
対応方法としては GCD(Grand Central Dispatch)を使って openUrl を呼び出すことで解決できます。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[[UIApplication sharedApplication] openURL:url]];
});
GCD は iOS におけるマルチスレッドの手法の一つです。処理したいタスクを Block で渡すことで逐次実行します。dispatch_async を使うことでタスクの処理を非同期で実行してくれます。同期処理させたい場合は dispatch_sync。
なぜ固まるのか原因が定かではないですが、これによってスムーズにブラウザを起動させることができます。
※ Qiita へも投稿しました。
Objective-C - push 通知からブラウザ起動させる際に注意すること - Qiita