TECH PLAY

株式会社モバイルファクトリー

株式会社モバイルファクトリー の技術ブログ

223

こんにちは。コーポレート・コミュニケーション室の id:kfly8 です。 この記事では、3/28(木)に開催された Gotanda.EM #1 のイベントについてお伝えします。 Gotanda.EM #1 主催は、モバファクでブロックチェーンチームのエンジニアリーダーをしている @tsukumaru と dely株式会社 のエンジニアリングマネージャーをしている @kr9ly さんです。楽しいイベントをありがとうございました! モバファクは、EMに関わる人たちがコミュケーションする場をサポートしたく、会場/懇親会のスポンサーをさせてもらいました。そして 積極採用中 です! どんなイベント? Gotanda.EM #1は、 五反田近郊で働いている人々や五反田に集結できる人々で開催する、エンジニアリングマネージャー(Engineering Manager)たちのためのライトな情報共有の場 とのことです。 イベントページに記載の通り、カジュアルで話しやすい雰囲気でした:D 京都(!) から @shiba_yu36 さんがいらしたので、「五反田近郊とは?」「京都も含めていきましょう!」という気持ちになりました。 受付は、アイコン入り名札で素敵! Gotanda.unity を参考にしているそう。 アイコン付きの名札だ〜 #gotandaem pic.twitter.com/8iAYeQH93C — kobaken (@kfly8) 2019年3月28日 席は島ごとに登壇者を配置するスタイル。アイスブレイクで普段の悩みを共有して、話しやすい雰囲気になってからLTが始まりました。 LT発表者の緊張をほぐし、場を温めるためにアイスブレイクを用意する、なにこの神運営 #gotandaem — 水月 涼 (@mizuki_r) 2019年3月28日 アイスブレイクでめっちゃ話した #gotandaem — 柴崎優季 (@shiba_yu36) 2019年3月28日 各LT LT #1 「あなたがエンジニアリングマネージャーと名乗る理由はなんですか?」 - @kr9ly 一番手は、主催のからくりさん。からくりさんが、マネージャーとしての役割を模索しながら、理想のチーム、マネージャーとしてのあり方をチームメンバーから教えてもらったという素敵な話。問題を抱え込まず、周りの人に頼っていく大切さを改めて感じました。 スライド公開しました! https://t.co/CgI2i6vuTf #gotandaem — からくり太郎 (@kr9ly) 2019年3月28日 LT #2 「副業エンジニアを中心とした開発チームとその勘所」 - @yamato 2番手は、 やまと さん。スタートアップでハイスキルな副業の人が多くいるチームで、開発の成果を出せるように工夫していることをタスク・コミュニケーションの切り口で具体的に聞け、とても実践的でした。 今日の資料こちらです! https://t.co/JDlBmfUuW4 #gotandaem — やまと (@kaacun) 2019年3月28日 LT #3 「マネージャー!きみは何者だ!?」 @mizuki_r 3人目は、 @mizuki_r さん。「EMって本当に必要?なくせたりしない?」という根源的な質問から始まり、チーム・組織の価値を最大化するためにエンジニアの職能独特の言葉・成果を、チーム・組織で理解できるよう促進し正しく評価できる必要があるという、とても考えさせられる内容で楽しかったです。 今日の資料です → マネージャー!きみは何者だ! #gotandaem https://t.co/SyWjZXhKuo — 水月 涼 (@mizuki_r) 2019年3月28日 LT #4 「経験から効率よく学習する」 @kfly8 4人目は、私でした。マネジメントでやるべきこと、優先順位を決めることは常に悩むことだと思います。ここでは育成において、学術的なモデルを利用して、現状の過不足をみてみませんか?という話をしてみました。 スライド公開しました #gotandaem / 経験から効率よく学習する https://t.co/S8IjBrG3RJ — kobaken (@kfly8) 2019年3月28日 LT #5 「グレードイメージ具体化のため昇格理由を公開する」 @shibayu36 最後は、 @shiba_yu36 さん。評価において納得感は大事ですし、目指す方向がイメージできた方が行動しやすい、そこで、グレードの昇格理由を公開し、知見として溜まるようにしたという話。会社に取り入れるためにどう説得したのか?公開したことで何か問題は起きなかったか?LTだけでは聞き足りない色々聞きたくなる話でした! “グレードイメージ具体化のため昇格理由を公開する - Speaker Deck” https://t.co/oMmOnM1SpW — 柴崎優季 (@shiba_yu36) 2019年4月1日 おわりに 以上、Gotanda.EM #1 のイベントレポートでした。#2 の開催もあるようなので、楽しみです!
アバター
こんにちは、ブロックチェーンチームでエンジニアリーダーをしている id:tsukumaru です。 最近の関心ごとは1on1です。 1on1は基本的に後輩の悩みを先輩が聞くという構図だと思います。逆に先輩の悩みを後輩が聞く機会はなかなか無いですよね。 チームや組織でお互いに悩みや考えが話せる関係は、メンバー間の繋がりを強くしていくと思います。ですが、いざ自己開示をしようにも、職位や立場などから話しにくいといった問題はありがちだと思います。 今回は、そんな課題に対して「カードゲーム」という形式を用いて悩みを共有する会を開催してみたので、その様子を紹介したいと思います。 経緯 先日、SELECKさん主催の「 Management Lab by SELECK 〜明日からの組織マネジメントを考える飲み会#1〜 」という勉強会に参加してきました。 SELECKさんが考えたカードゲームを使って自分の悩みを共有し、解決していこうという内容です。 自分は新卒育成に関しての悩みを共有しましたが、とても楽しく自分の悩みの共有、深掘りができたと思います。SELECKさんありがとうございます。 勉強会終了後カード一式が各参加者に配布され、ぜひ自分の会社でもやってみてくださいということだったので、今回社内勉強会の時間を使ってカードゲーム会を行いました。 カードゲームについて ゲームに使うカードは、以下のように5種類のカードから構成されています。 ルール 4~5人程度のグループに分かれ、順番に悩みを発表していきます。一人につき3つのフェーズがあり、順番に行なっていきます。 登場人物 主人公 (悩みを発表する人、1人) サポーター (悩みを深掘りする人、3人~4人) カードの種類 Objective 主題的なもの 「1on1」とか「評価制度」とか Theme Objectiveの中のどんな話なのか 「違和感を感じること」とか「レベルアップしたこと」とか Speaker Reflection テーマについての質問 「それは本当に悪いことですか?」とか Listener Reflection 主人公に対してのFB 「主人公が次にやることは何だと思いますか?」とか Next Step 気づきを言語化する 「新たに見えた選択肢は?」とか ゲームの流れ 最初の主人公を決める 主人公がObjectiveとThemeからカードを選んで決める 悩みの概要を2分くらいで話す 各サポーターがSpeaker Reflectionからカードを1枚選んで質問し、悩みを深掘りする 終わったら各サポーターがListener Reflectionからカードを1枚選び、主人公にFBする 次の主人公に回す 全員終わったら、Next Stepからカードを1枚ずつ選び、気づきを発表する 実際にやってみた 弊社でこのゲームを行なってみた様子です。 メンバーは役員1名+マネージャー2名+シニアエンジニア1名の計4名で行いました。 また、今回は多くのメンバーにFBしたかったので、オーディエンスとして現場のメンバーも何人かいる状態で開催しました。 今回は役員の方の結果をご紹介します。 選んだObjectiveとThemeは、「ビジョン・ミッション・バリュー」と「その仕事を通じて成し遂げたいこと」。 役員としてビジョン・ミッション・バリューを会社に浸透させていきたいというお話でした。 役員の方の考えや気持ちを改めて聞くことができ、勉強会の場にいたメンバーの気がより一層引き締まったような感覚がありました。 それに対して深掘りのフェーズであるSpeaker Reflectionでは、理想状態や自分の役割、ハードルとなっていることはなにかという質問があげられました。 普段の会話では率直に聞きにくいことも、カードゲームという場であることで、思い切って聞きやすくなっていたと思います。 そして主人公へのFBのフェーズであるListener Reflectionでは、ネクストアクションの提案や自分の経験から役立てられることなどが話され、役員の方自身も新たな発見を得ることができていました。 他3名の方の結果は以下の通りです。評価や目標達成という似たObjectiveが選択されましたが、それぞれ異なる視点からの悩みが共有されていました。 最後に全員で気づきを発表して終わりです。 仲間に協力を仰ぐべきことや、明らかにするべきことなど、それぞれの悩みに対してのネクストアクションを出すことができました。 カードゲーム会を開催してみての感想 今回マネージャー陣の悩みや考えを共有する場としてカードゲーム会を開催しましたが、聞いている側からすると認識があっているところ合っていないところがわかったり、意外な考えが聞けたりなど発見がたくさんありました。 普段は技術的な話題が多い社内勉強会ですが、こういった会をきっかけにマネジメント系の勉強会も増えていったらいいなと思っています。 また、社内勉強会の枠である1時間で行ったため、ゲーム進行がかなり忙しくなってしまいました。やるときは1時間半~2時間くらいとれると、じっくり話す時間がとれると思います。 まとめ 社内勉強会の時間でマネージャー向けカードゲーム会を開催しました。 今回参加されたマネージャー層の方たちにとっては、自分の悩みや考えを共有することで内省のきっかけとなったり、他の方がどんな悩みや考えを持っているかを知る機会となりました。 オーディエンスとして参加したメンバーは、マネージャー層の方たちがどういった視点や考え方をもっているのかを知ることで、疑問が解消されて納得感につながったり、自分の向いている方向の軌道修正をするきっかけになるなど、今後の行動を見直す機会となりました。 今回はカードゲームという形を用いましたが、どんな形式でもいいので、マネージャーや経営層の方同士、もしくはマネージャーと現場のメンバーなどで悩みや考えを共有する機会をつくってみると新しい発見があるかもしれません。
アバター
こんにちは。 id:kfly8 です。 先日、 YAPC::Tokyo 2019 というカンファレンスが開催され、総勢37名のスタッフで運営されていました。その運営にモバファクのメンバーが私含め5人スタッフ参加させてもらったので、その感想をお伝えしていきたいと思います。 早速ですが、インタビューをしていきます! *1 Q. 普段はどんなお仕事をしていますか? numa_yk : 広報とIRをやっています。 N : 採用チームにいます。 xiang_ping : ブロックチェーン関連のチームで、新規サービスのプロジェクトリーダーをやっています。エンジニアではないので、主に企画とディレクションがメインです。 odan3240 : ブロックチェーンチームでエンジニアとして働いています。お仕事は、DApps開発キットのUniqys Kitの開発や、新規サービスの開発をやっています。 kfly8 : 採用、育成、広報、組織開発など事業を横断するようなことをやらせてもらっています。が、最近はずっとYAPCのお仕事をしていました:D Q. どうしてスタッフ参加してみようと思ったのですか? numa_yk : YAPCは昔から社内のエンジニアたちがたくさん参加しているイベントで、モバファクのエンジニアがどんなところでわくわくしているのか見てみたく参加しました。会社で提供しているサービスはエンジニアなしでは作れないので、どんな活動をしているのか興味がありました。 N : こばけんさんに誘われて。(※こばけんは、kfly8の呼称です) xiang_ping : 社内で参加する方にお誘いいただきました。エンジニアイベントなのでこれまで参加を検討したことはなかったのですが、非エンジニアの立場で参加することで得られる体験もあると思い、今回の参加を決めました。 odan3240 : 会社がPerlを主に使用していることもあって、YAPCに興味があったことが大きいです。また、ACM-ICPCという競技プログラミングの大会のアジア地区予選のボランティアをやった経験があり、技術カンファレンスのボランティア自体にも興味がありました。 kfly8 : Perlが好きだから。Perlコミュニティが好きだから。 Q. スタッフとしてどんなお仕事をしましたか? numa_yk : 受付をやっていました N : 当日は受付や弁当、荷物の配送関連をしていました。バックパネルなど発注も行いました! odan3240 : ホールのスタッフをしていました xiang_ping : 受付中心に動いていました。 kfly8 : 運営委員長でした。当日は会場をふらふらしてイレギュラーなことがあったら対処をしていました。それまでは、主にスポンサー様、イベント会社の方、会場の方とのやりとりを行なっていました。 Q. 参加して良かったことを教えてください! numa_yk : 活気があって楽しかった!(雑) numa_yk : 非エンジニアからすると一見入りにくい感じかと思いきや、一緒にわいわいすると職種関係なく一つのもの作れますね。 N : いろんな企業のいろんな職種の人と話す機会はそうそうないので、雑ですが楽しかったです。 xiang_ping : 普段であれば繋がることがない方と交流できたことです。特に他社のエンジニアの方とお話しする機会はほぼないので、どのような仕事をしていて、今後どのようになりたいかなど興味深い話を色々と聞くことができました。 odan3240 : タダ飯 odan3240 : スタッフ間で調整すれば気になるセッションを聞くことが出来たのが嬉しかったです。AWS X-Rayを用いた分散トレーシングの話に興味があったので、実際に聞きに行きました。 kfly8 : 楽しい!って声が聞けて良かったです。過去にもスタッフをやらせてもらったことがあるのですが、今回、正直大変だったこともあり、心に沁みました。 Q. 逆に悪かったこと、改善した方がいいと思ったことがあれば教えてください! xiang_ping : 前日の準備が大変ですね、、Tシャツの準備やノベルティ詰めなどもっと効率的にやれたらいいなと思いました。ただ、作業中に色々とお話しできるのはよかったです。また受付に関して、一部QRコードによる自動化がありましたが、Tシャツの配布や企業スポンサー対応などアナログな部分もあったので、その辺りをもっと効率化できればいいかと思いました。 odan3240 : 朝が弱いタイプなので集合時間が早めだったのがつらかった... kfly8 : 個人的な話ですが、トークがほぼ見れなかったので動画にしっかり残したい..! Q. 参加してみてわかったこと、気づきがあれば教えてください! N : 交流って大事ですね。最後の懇親会などはとっても楽しかったです! xiang_ping : エンジニアでなくても、その場での出会いや、やりきった後の達成感が得られるので、参加したことのない人に勧めたいです! odan3240 : 参加する前は技術カンファレンスにスタッフは難しいというイメージがあったため、ボランティア応募することに抵抗がありました。しかし実際参加してみると難しいことはなく、カンファレンスに携わることが出来ました。 kfly8 : YAPCのやり方もひとつじゃない!って 記事 を書きました! Q. 感想をひとこと! numa_yk : たのしかった! N : 京都行きましょう!w xiang_ping : また関東近辺で開催することがあれば参加したいと思います! odan3240 : YAPC Kyoto楽しみ! kfly8 : 楽しかった! あとがき よくよく考えてみると不思議ですよね。 YAPCというとがっつりエンジニア向けのイベントです。トークは全部技術トークです。 今回、スタッフ参加した5人のうち3人は非エンジニアです。非エンジニアが興味を持って、休日に何を話しているかわからないイベントに参加し、朝から晩まで楽しんでいるなんて、不思議じゃないですか? *2 自分の立場は、運営委員長であったり、弊社の人間であったりして、客観的ではないかもしれないですが、素敵なことだなーと思います。 技術コミュニティにスタッフとして参加することは、もしかしたら敷居のあることに見えるかもしれませんが、 今回のインタビューを通して、改めて「気にしないで!楽しめるよ!」ってことを声を大にして言えるなーと思いました。 *1 : 自分で自分にインタビューするのは変ですが気にしません *2 : もちろんエンジニアがスタッフ参加しても楽しめました:D
アバター
この記事は モバイルファクトリー Advent Calendar 2018 の24日目の記事です。 はじめに メリークリスマス! エンジニアの id:Carimatics です。 突然ですが、シェルスクリプトは便利な言語ですね。 Unix系OSであればほぼ標準で利用でき、シェルスクリプトにより移植性の高いコードを記述することができます。 また、シェルコマンド自体も強力なものが数多く用意されており、処理を簡潔に表現することも可能です。 一方で、シェルスクリプトは普段から慣れ親しんでいなければ読み書きが難しい言語でもあります。 1 というのも、最近久しぶりにシェルスクリプトを書いたのですが、書き方に迷う場面が多々あったのです。 めったに使わない構文であれば仕方ないとも思えるのですが、if文やfor文を書こうとするたびに手が迷ってしまい、大変もどかしい思いをしました。 また、シェルスクリプトの規模が大きくなると、保守性も困難になりがちです。 ということで、Pythonをbetter shell scriptとして利用できると楽そうだと考えました。 Pythonであれば比較的読み書きしやすい構文で書くことができます。 他にもRubyやPerlなどがある中でどうしてPythonなのかと言うと、これは趣味です。 シェルコマンドを軽くラップできれば漸進的にスクリプトを改善していけると考えたため、本記事ではとりあえずシェルコマンドを簡単に叩けるようになるまでを目標にしました。 動作確認環境 Python 3.7.1 方針 Pythonの標準ライブラリに含まれるもののみを利用する ひとまず3.x系のみ対応する Pythonでシェルコマンドを実行する 簡単な方法 シェルコマンドを実行すること自体は簡単で、 subprocess モジュールを利用することでシェルコマンドを子プロセスとして実行できます。 subprocess モジュールでは、サブプロセスの実行は run() 関数を利用するのが推奨されています。 import subprocess subprocess.run( 'ls' ) 注意点として、 run() で引数に文字列を渡す場合、引数なしで実行されるプログラムでなければなりません。 例えば、以下のように、コマンド引数をしているとエラーになります。 import subprocess subprocess.run( 'ls tmp' ) 実行すると以下のエラーが出ます。 FileNotFoundError: [Errno 2] No such file or directory: 'ls tmp': 'ls tmp' ls tmp というプログラムを実行しようとして失敗しています。 シェルコマンドに引数を渡す場合には、 run() にトークン区切りのリストを渡す必要があります。 import subprocess subprocess.run([ 'ls' , 'tmp' ]) これで ls tmp を実行することができます。 このような場合、 shlex モジュールを用いて、コマンド文字列をリスト化できます。 import shlex import subprocess cmd = 'ls tmp' tokens = shlex.split(cmd) # => ['ls', 'tmp'] subprocess.run(tokens) トークン区切りにする以外には、 run() に対して shell=True を渡すことで、シェルコマンド文字列をそのまま実行することができます。 import subprocess subprocess.run( 'ls tmp' , shell= True ) しかし、この場合はシェルを実際に呼び出すことになるため、シェルインジェクションが発生する可能性があるので注意が必要です。 例えば、以下のような攻撃が発生し得ます。 import subprocess def ls ( dir ): cmd = f "ls {dir}" . subprocess.run(cmd, shell= True ) ls( '; rm -fr tmp' ) これは、シェルで ls ; rm -fr tmp を実行することになり、tmpファイルを削除されてしまいます。 shell=True を利用する場合は、外部からの入力には十分に注意しましょう。 パイプを利用する shell=True する方法 shell=True することで、パイプを利用することは可能です。 ただし、前述の通りシェルインジェクションが発生する可能性があるため、利用には十分ご注意ください。 import subprocess def ls ( dir , filter ): cmd = f "ls {dir} | grep {filter}" subprocess.run(cmd, shell= True ) ls( 'tmp' , '.md' ) shell=True しない方法 そこで、 shell=True を利用せずにパイプを利用するには、 subporcess.Popen を利用します。 上記と同等の結果を得るには、以下の用に書きます。 import subprocess import shlex def ls ( dir , filter ): cmd_ls = f "ls {dir}" cmd_grep = f "grep {filter}" p1 = subprocess.Popen(shlex.split(cmd_ls), stdout=subprocess.PIPE) subprocess.Popen(shlex.split(cmd_grep), stdin=p1.stdout) p1.stdout.close() ls( 'tmp' , '.md' ) かなり冗長になってしまいました。 Popen を利用する場合は、使いやすくスクリプトを整えてあげるか、セキュリティリスクを考慮した上で shell=True の利用を検討しても良いかも知れません。 まとめ かなり駆け足になってしまいましたが、Pythonでシェルコマンドを利用する方法について簡単に紹介しました。 subprocess を利用する以外にも、 shutil 、 os 、 pathlib 、 glob などのモジュールを組み合わせることで、よりbetter shell scriptらしい可読性の高いコードが書けるようになりそうです。 最後までお読みいただきありがとうございました。 それでは良いお年を! もちろんシェルスクリプトに限った話ではありませんが、その傾向が強い言語だと思っています。 ↩
アバター
モバイルファクトリー Advent Calendar 2018 最終日25日目担当の id:tsukumaru です。 はじめに 私は今年の8月くらいからチームの新卒育成のポジションを任されているのですが、育成に関する経験はこれが初めてでこの4ヶ月間さまざまな学びがありました。 1on1においてメンティーから話を引き出すことは重要ですが、メンター側からの問いかけ方次第で、引き出せる話に違いがあることに気づきました。 ここでは、その気づきについて書きたいと思います。 ※メンティーには内容の公開許可をもらっています。 学んだこと 大丈夫そう?は使わない 後輩「〇〇のときどうすればいいかわからないです」 自分「こうすればいいかもね」 後輩「なるほど」 自分「これで大丈夫そう?」 後輩「大丈夫そうです」 私が1on1でメンティーになにかをティーチングしたり、目標の進捗について確認した後、「これで大丈夫そう?」とよく聞いていました。伝えたことに対してわかったかどうかを確認するためです。 するとだいたい「大丈夫そうです」と返って来ます。それで本当に大丈夫ならいいのですが、私の経験では次の週になっても同じ悩みを抱えていることがありました。 「これでいい?」という類のYes/Noな質問を迫られた時、Yesと答えてしまう人は多いと思います。これには「この人が言うなら」とか「せっかく教えてくれたし」のような気持ちがあったり、本当に自分では問題ないと思ってYesと答えている場合があります。前者は新卒社員が3年目の先輩から言われればなおさらですし、後者はメンティーの見えてない部分をメンター側からティーチングしてあげる必要があると思います。 どちらにせよ、メンター側がメンティーの気持ちを汲み取ってあげることが大事になってきます。 よりよいやり方 後輩「〇〇のときどうすればいいかわからないです」 自分「こうすればいいかもね」 後輩「なるほど」 自分「次〇〇なことが起こったらどうする?」 後輩「△△してxxしてみます」 自分「よさそう」 伝えたことを実践するための具体的なプロセスを聞くといいです。曖昧な部分を曖昧にせず、メンティーに具体化させるようにすると、メンティーとしては次やることが自然と見えてきますし、メンターとしても伝わったことが確認できます。 もし実践できていなくても、「△△まではできた?」など問題をさらに分解していけます。 やることは指示するのではなく聞き出す 自分「今回話したことから、来週は〇〇と△△をやっていこう」 後輩「はい、わかりました」 .. 自分「先週話したことの進捗どう?」 後輩「まだできてないです」 1on1をして最後に次やることを確認する際、メンター側が主導してしまったことが多々ありました。 メンター側としては1on1を仕切って後輩を指導している気持ちになり満足してしまいます。メンティー側としてもその場ではわかったつもりになり「はい」と答えますが、きちんと理解できておらず実行に移せなくなってしまいます。 よりよいやり方 自分「今回話した次やることを確認してみよう」 後輩「〇〇をやってから△△をやります」 自分「がんばろう」 .. 自分「先週話したことの進捗どう?」 後輩「〇〇まではできました」 前項の「大丈夫そう?」のときとほぼ同じですが、メンター側が仕切るのではなく、メンティー側に確認させるとよいです。自分でやることを言葉に出すことで、理解を促進させることができると思います。 自分は伝えたと思っていたけど、実際は相手に伝わっていなかったということは1on1ではけっこうあるので、常に意識していくといいと思います。 慣れない間は1on1の流れを形式化させる 自分「今週やったことは?」 後輩「〇〇をやりました」 自分「そこでわかったことは?」 後輩「△△という知見を得ました」 自分「なるほど。それを踏まえて次なにする?」 後輩「△△の知見を使ってxxな部分は効率化できそうなのでやってみます」 まだメンターとして1on1の経験がないとき、1on1をどう進行していけばわからないという状態になりました。私なりにメンティーの話の中で曖昧な部分があったら深掘りするということは意識していましたが、手探りで行なっている感じがいつまでも消えませんでした。 そこで見つけたのが、YWTという振り返りのフレームワークです。やったこと、わかったこと、次にすることの3つの頭文字をとってYWTとなっています。 https://hisa-magazine.net/blog/manabutikara/ywt/ このYWT、なにがすごいかというと、3つを順番に聞いていくことでメンティーに自然と内省を促すことができてしまいます。メンターとしても最初のうちはこのフレームワークにのっとり、3ステップで聞いていくと進行のリズムが掴みやすくなるかと思います。 YWTを導入するときはいきなりやり始めるのではなく、メンティーに「メンターとしてまだ1on1に慣れてないから、こういうフレームワークを使いたい」と合意をとった方がいいです。 私は勝手に導入してやり始めてしまったため、自分が期待しているような会話ができず、メンティーの内省につながらないことが多々ありました。 よりよい方法 後輩「〇〇ということがあって大変だったんですよ」 自分「へ〜、そこでなにか学べたことはある?」 後輩「△△ということは学びとしてありました。」 自分「その学びは次に生かしていけそうだね。具体的なイメージある?」 ずっと3ステップのままだと1on1が作業になってしまいますし、メンティー側の主体性(話したいこと)を奪ってしまう可能性もあります。慣れてきたらYWTを意識をするだけにしておき、何気ない会話の中で深掘りしたい時に使うようにします。 目標は自己評価基準で考えてもらう 自分「目標進捗どんな感じ?」 後輩「いまこんな感じです」 自分「あまり進捗よくないね。どこからやろうか」 後輩「これとかなら...」 私のメンティーは目標をあまり自分ごととして捉えられていませんでした。会社から言われたから目標設定をして、無理やり目標について考えている感じでした。 これは珍しいことではなく、実際私も新卒の頃は自分ごととして捉えられず、苦労して目標設定をしましたし業務中もあまり目標を意識できていませんでした。 自分ごととして捉えられていない原因として、私は「会社からどう評価されるか」を意識していることが原因だと考えています。 もちろん設定した目標の達成度を評価するのは上長ですし、会社に属している以上会社に対して貢献していくことは重要なことです。しかし今回のような場合、新卒メンバーにとって大事なのは目標をまず自分ごととして捉えてもらい、その次のステップとして会社を意識していくことが大事だと考えました。 よりよいやり方 自分「目標進捗どんな感じ?」 後輩「いまこんな感じです」 自分「自己評価はどのくらい?」 後輩「まぁまぁですね」 自分「その自己評価あげるにはどうすればいいと思う?」 後輩「これはまだできてないのでやってみようと思います」 目標の進捗を聞く時は「どうすれば達成できるか」ではなく「どうすれば自己評価があがりそうか」を聞くとよいです。 他者からの評価ではなく、「自分としての評価はどうか」を常に意識することで目標に対する意識を少しずつ自分に近づけていけると思います。 ただ、すぐに効果があるわけではないので、気長にサポートしていくことが大事です。 何度伝えても改善しない場合、自分の感情を伝える 後輩「本を読んでアウトプットするという目標を立てたので本を読みます」 自分「がんばろう」 .. 自分「本どんな感じ?」 後輩「読めてないです。今週で少し読めるようにがんばります。」 .. 自分「本どんな感じ?」 後輩「勉強会など忙しくて読めてないです。会社に朝早く来てよみます」 .. 自分「本どんな感じ?」 後輩「読めてないです。今年中のアウトプットも厳しくなりました」 自分「そっか〜...」 今週までになにかをしよう等、1on1で伝えても実行されず、気づいたら査定の時期になっていたということがありました。 目標の変更や 社内勉強会 の時間で輪読会をすることも提案しましたが、自分で読みきりたいという気持ちを持っていたためそのまま継続する形になりました。 メンターとしていろいろサポートしてきたつもりでしたが、うまくサポートできず「約束したことを守れなかった」という状態になってしまったことがとても悔しかったです。 よりよいやり方(かもしれない) 自分の感情を素直に伝えてみましょう。 悲しい、悔しい、ショック、などなど。ある程度関係性ができている人から悲しいなどと言われれば、やらないとという気持ちになる可能性があります。 このやり方は「単純にやる気がなかった」という場合に効きそうなものであり、もしかしたら原因は他にあるかもしれません。深掘りにもう少し時間を使うべきだったとは感じています。 実際この方法で効果が見えたわけではなく、いまでもよりよいやり方を模索中です。 まとめ 育成担当として、1on1をするなかで学んだことをご紹介しました。これから育成に取り組む方、すでに取り組んでいる方のお役に少しでも立てたら幸いです。 モバイルファクトリー Advent Calendar 2018 を最後までお読みいただき、ありがとうございました! 🎉
アバター
この記事は、 モバイルファクトリーAdvent Calendar 2018 23日目の記事です。 前日の記事は、 id:masasuz さんの モバイルファクトリーのインフラアーキテクチャ でした。 こんにちは、 id:yumlonne です! 最近、テストの重要性をひしひしと感じているので、テストについての記事を書くことにしました。 他の記事でも紹介されていますが、モバイルファクトリーの主要プログラミング言語はPerlです。 Perlは動的型付け言語であり、プログラマがデータ型を管理しなければならないため、非常にテストが重要です。 今回は、最近個人的に注目しているテストモジュールを2つ紹介します。 Test::Doctest Test::Doctest は、Python由来のテストモジュールです。 その名の通り、ドキュメント内にテストを仕込むことができます。 ここでいうドキュメントとは、平たく表現すれば「ソースコード内の関数説明などに用いられるコメント」のことです。 Perlでは # を使ってコメントを書くことができますが、関数説明には POD を使います。 Test::Doctestは、POD内に埋め込まれたテストを実行します。 使用例 コード pod内の >>> の後ろにコードを書き、下の行に期待する結果を書きます。 package Hoge ; use strict ; use warnings ; use utf8 ; =encoding utf8 =head1 METHODS =over =item plus1 数値に1を足してくれる関数 >>> plus1(30) 31 >>> plus1(-1) 0 =back =cut sub plus1 { my $n = shift ; return $n + 1 ; } 1 ; テスト コマンドラインからテストを実行する例を載せますが、 .t ファイルにすることもできます。 詳細は SYNOPSIS を参照してください。 $ perl -MTest::Doctest -e run lib/Hoge.pm 1..2 ok 1 - plus1 1/2 (lib/Hoge.pm, 16) ok 2 - plus1 2/2 (lib/Hoge.pm, 19) Test::Doctestまとめ 以下に私なりの長所・短所をまとめました。 短所を認識し、複雑なものは別のファイルに書くなどの対応をすれば、十分に使う価値のあるモジュールだと思います。 長所 ソースコードと同一のファイルにテストが書けるため、敷居が低い 関数の入出力例を示すことができる DoctestのためにPODを書くというモチベーションが生まれる 短所 複雑な関数や文字列での表現が難しい(あるいは長くなる)入出力データを扱うのが難しい 書きすぎるとソースコードの見通しが悪くなる Test::RandomCheck Test::RandomCheck は、Haskell由来のテストモジュールです。 このテストモジュールには大きな特徴が2つあります。 テストデータをモジュールがランダムに生成する テストするのは関数の 性質 使い所は限られていますが、非常に簡単にテストができます。 使用例 コード ここでも先程テストした plus1 関数のテストをします。 package Hoge ; use strict ; use warnings ; use utf8 ; sub plus1 { my $n = shift ; return $n + 1 ; } 1 ; テスト use Test::RandomCheck; use Test::RandomCheck::Generator; use Test::More; use Hoge; # 受け取りたい引数の型 my $arg_type = integer; random_ok { # ここでinteger型の値を受け取れる my $arg = shift ; # plus1の性質をチェック Hoge::plus1( $arg ) == $arg + 1 ; } $arg_type ; done_testing; テスト結果 $ perl -Ilib t/randomcheck,t ok 1 1..1 テストの結果の出力は非常に寂しいものの、この例では random_ok ブロックは様々な整数値で202回実行されていました。 Test::RandomCheckまとめ 以下に私なりの長所・短所をまとめました。 使い所は難しいですが、うまく使えればとてもパワフルなモジュールだと思います! 長所 テストデータを考える作業をスキップできる 関数が持つ性質を表明できる( reverse した配列を reverse するともとに戻るなど) 短所 実験的なモジュールである テストデータがランダムであるため、失敗したテストケースの特定が難しい(Haskellではテストデータを再現できる) 汎用的な関数以外はテストしにくい おわりに 今回はPerlのテストモジュールを2つ紹介しました。 テストを充実させるために、まずはテストを楽しむことが重要だと思っています。 皆さんもぜひ、紹介したテストモジュールなどを使って、テストを楽しんでみてはいかがでしょうか! 明日は id:Carimatics さんです。お楽しみに!!
アバター
この記事は モバイルファクトリー Advent Calendar 2018 の22日目のものです。 このブログではお初にお目にかかります。インフラチームの id:masasuz です。 モバイルファクトリーでは現在AWSと物理データセンターを使用しています。 モバイルファクトリーではもともと物理データセンター使用しており、既存のサービスはその上で構築されております。 数年前からAWSを本格的に利用し始めて新規のサービスは基本的にAWSで構築します。 AWSで構築されたサービスは要件によって様々なアーキテクチャになっています。 今回はある程度共通化されている物理データセンターにあるサービスのインフラアーキテクチャについて述べていきます。 アーキテクチャ リバースプロキシ Nginx アプリケーションサーバ Plack/PSGIサーバ(Starlet, Gazelle) + Server::Starter 静的コンテンツサーバ Nginx + CDN Amazon S3 ストレージサーバ MySQL(or Percona Server) Redis キャッシュサーバ Memcached Redis ジョブキューサーバ Gearman Qudo Resque 個々のミドルウェアは置いておいて、構成としては特別なものを使っていないと思います。 モバイルファクトリーとして特色のあるアプレケーションサーバに関して少しだけ詳しく述べたいと思います。 サーバ全般 サーバOSはLinuxを使っており、ディストリビューションとしてはUbuntu Serverを使用しています。かつてはDebianを使用していましたが、全て置き換えられています。 ミドルウェアは可能な限り標準のインストールで入るものを使用します。 標準でパッケージが用意されていない場合はdebパッケージを作成して、社内のaptサーバからインストールできるようにしています。 サーバ構築毎にビルドするということはしないようにしています。 つまり何かインストールするときは基本的に aptitude install ないし apt-get install ですむようになってます。 Perl モバイルファクトリー で作られているプロダクトは最近は別として、ここ10年くらいはPerlで書かれています。 かつてはOS標準についてくるSystem Perlを利用していましたが、 OSアップグレードのたびにバージョン変わるのはつらい 新しいバージョンのPerlを使いたい などの理由で、独自にパッケージングしたperlを使うようにしています。 mf-perl5.xx の名前でパッケージングしてプロジェクト側で使いたいバージョンのperlを選択できるようにしています。 Perlモジュール Perlモジュールのバージョン管理に関しては、cpanfile.snapshotを元に carton install --deployment でインストールすることにより、 依存パッケージも含めて同じバージョンのPerlモジュールがインストールされるようになっています。 基本的には公開されているCPANのモジュールを使うようにしていますし、それが推奨されてますが、 社外に公開できないロジックが含まれていたりするものに関してはdarkpan(プライベートなCPANレポジトリ)を立ててそこからインストールするようにしています。 アプリケーションサーバ かつてはApache + mod_perlで動いていましたが、公開されているサービスは全てPlack/PSGIに置き換えられています アプリケーションサーバのデーモン管理にSystemdを利用しています。これもOS標準の起動方法に合わせるためです。 Start::Server経由でPlack/PSGIサーバを起動することにより、ホットデプロイできるようにしています。 アプリケーションサーバとしては主にStarletを使っていますが、新しめのプロジェクトではGazelleも使われています。 WebフレームワークとしてはSledge、Splite、Amon2が使われています。SpliteはPSGI対応したSledge風のフレームワークです。 社内独自フレームワークです。 SledgeやSpliteは長めに運営されているサービスで使われており、現在では新規でPerlのプロジェクトを作る場合は、Amon2が使われております。 結びに モバイルファクトリーで主に使われているアーキテクチャについてざっくりと書いてきました。これは現在の姿で、課題があれば日々変化していっています。 一昔前であれば、デーモン管理はUpstartを使っていましたし、リバースプロキシはPerlbalを使っていました。 長年運営しているサービスもあるので、ドラスティックに変更はできませんが、少しずつよりよい形に改善していってます。 また、冒頭で述べたように新規のサービスではAWSで構築することが多く、AWSに合わせたアーキテクチャを構築しています。 これに関してはどこか別のところで述べられたらと思います。 また、今回はざっくりでしたが、掘り下げて知りたいこと等ありましたら、またどこかの機会で述べたいと思います。 明日は、 id:yumlonne さんです。
アバター
こんにちは! モバイルファクトリー Advent Calendar 2018 - Qiita 20日目担当の id:Ruby_on_JRs こと id:pikkaman です。この10月にモバイルファクトリーに新卒で入社し、エンジニアとして働いています。 はじめに 唐突ですが、私は入社直前の夏、全国のJR線で一筆書きをしたとき最も営業キロが長くなるルートで旅行する、いわゆる「JR線最長片道切符の旅」を自前のアルゴリズムで計算し、実際にそのルートで1ヶ月半かけて旅した後にそのまま入社していまして( Ruby on Japan Railways @最長片道切符(@Ruby_on_JRs) - Twilog )、全国4000の駅の思い出を集めながら日々全国の駅と路線の構造に想いをはせていました。 せっかくモバファクのアドベントカレンダーに参加することになりましたので、また別の路線図の最長経路問題について書くことに決めた次第です。 気付くと開いていた東京メトロのサイトの運送約款に関するページ 運送約款 | PASMO・定期・乗車券 | 東京メトロ に掲載されている、きっぷのルール等を定めた旅客営業規程を読んでみると以下の記述を見つけられます。 第45条 第43条の規定にかかわらず、次の区間内各駅発着の場合、又は同区間を通過する場合の片道普通旅客運賃は、最も短い経路のキロ程によって計算する。 第82条 第45条に掲げる区間内各駅間相互発着又は通過となる普通乗車券又は回数乗車券を所持する旅客は、その区間内においてはその乗車券の運賃計算経路にかかわらず、う回して乗車することができる。 つまり、 特定の条件を満たすとき出発駅と到着駅から決まる運賃を払えば、途中どんな経路を通ってもよい ということです。世界有数の複雑な路線を持つ東京メトロであれば、きっと思いもよらない長い迂回路を取ることができるだろうと期待できます。この最長経路を考えてみることにしました。 この記事では以下の条件を満たすような経路をコンピュータを駆使して求めることを目標にします。 出発駅から到着駅までの経路の営業キロの和が最長となるような迂回経路を求める。 運賃は東京メトロの片道乗車券の上限である310円までとし、初乗り運賃にこだわることはしない。 出発駅の改札を通った後は、到着駅まで改札を出ることはない。 既に通過した駅にもう一度訪れてゴールとする経路は考えない。 条件2、3、4を変えることでさらに面白い問題にすることが可能ですが、今回は簡単のため単純な条件を採用しました。 注:読者から条件4が不足していることを指摘していただきました。ありがとうございます。 グラフ問題ライブラリGraphillionの導入 Graphillionとは 最長片道経路の探索にはPythonライブラリのGraphillion( Home · takemaru/graphillion Wiki · GitHub )を採用しました。GraphillionはPythonとC++を組み合わせて作られた高速にグラフ問題を解くためのライブラリです。具体的なアルゴリズムは割愛しますが(これも面白いのでまたの機会に!)、ライブラリの威力は次の動画でで実感できるでしょう。 youtu.be youtu.be 元々私はC#で実装した自前の最長経路探索アルゴリズムを使っていたのですが、この記事を書くに当たってそれより数十倍高速なGraphillionを知ってしまい、思わず乗り換えることになりました。これがあればJR線最長片道切符の旅の経路計算で度重なる領域分割に苦労することもなかったでしょう。 おまけに、早口で言うと「グラフ理論」に聞こえるところもおしゃれで好きです。 インストールとサンプルの実行 Grapillionのインストールは pip で簡単に行うことができます。また、Python2, Python3の両方に対応しています。(ただし日本語の情報はほとんどがPython 2であることに気をつけてください。英語ではwiki等もPython 3にアップデートされています) 公式GitHubリポジトリのwikiに記載されているJR山手線・中央線を単純にしたグラフ問題で ( Example codes · takemaru/graphillion Wiki · GitHub )、実際のGraphillionの使い方を見ていきます。なお、説明のため問題と解答を一部改変しています。 ごく一部の例外を除いて、旅客営業を行う日本の鉄道路線では駅間を双方向に列車が行き来しています。そのため駅をノード、路線をエッジとする無向グラフとして路線図を見ることが可能になります。 さらに、最長片道経路問題を解くにあたっては各駅間の営業キロが必要になるので、営業キロを重みとする重み付き無向グラフを採用します。 +--(11.3)------------------------- 上野 | | | (3.6) | | 新宿 ---(3.7)--- 四ツ谷 ---(6.6)---- 東京 | | | (6.8) | | +--(10.6)------------------------- 品川 括弧内の数字が駅間距離を表しています。 この路線図に対して以下の問題を解きます。 東京から品川までの経路は何通りでしょう? 東京から品川までの最長経路はなんでしょう? 四ツ谷を経由して東京から品川まで至る経路には何があるでしょう? これらの問題はGraphillionを利用することで簡単に解くことができます。 まず、エッジを (出発駅, 到着駅, 駅間距離(重み)) の形のタプルで表し、リストに格納します。 そのリストを用いて set_universe() でグラフを生成すれば準備完了です。 from graphillion import GraphSet as gs universe = [( '東京' , '品川' , 6.8 ), ( '品川' , '新宿' , 10.6 ), ( '新宿' , '上野' , 11.3 ), ( '上野' , '東京' , 3.6 ), ( '東京' , '四ツ谷' , 6.6 ), ( '四ツ谷' , '新宿' , 3.7 )] gs.set_universe(universe) まずは東京から品川までの経路を列挙したグラフセット(条件を満たすグラフの集合)を生成します。 paths = gs.paths( '東京' , '品川' ) これだけで東京から品川までのすべての経路が分かりました。第1問は paths の len を取れば求められます。 print ( len (paths)) 続いて、全経路 paths から max_iter() を呼び出すことで重みの降順でソートされた経路を取り出すことが可能になります。よって第2問はその最初の経路を取ればよいことになります。 print ( next (paths.max_iter())) 最後に、あるノードを通過するような経路を including() によって抽出することができます。第3問はこれを使って四ツ谷を通るようなグラフセットのみを得ることができます。 print (paths.including( '四ツ谷' )) グラフセットの選択により複雑な条件を課すことで、グラフで表現できる様々な問題に対処できます。 Graphillionを使って最長片道経路を求める 前処理 ここからは本題の東京メトロの路線図で最長経路問題を解いていきます。 まずはGrapillionに路線データを読み込ませてグラフにします。 残念ながら駅間距離を取得するAPIを見つけることができなかったので、各種資料にあたって調べたものを手動でCSVにまとめます。 from to dist line 中目黒 恵比寿 10 日比谷線 恵比寿 広尾 15 日比谷線 広尾 六本木 17 日比谷線 六本木 神谷町 15 日比谷線 … … … … これを2次元データの解析に便利なライブラリ pandas を用いて読み込み(今回はCSVを読んでいるだけですが)、すべてのグラフの元になるユニバースを生成します。 なお、上のJR線の例とは違って、後に最長経路の距離を求めるために、タプルで表したエッジをキーとするディクショナリとして重み weights を持っています。 import pandas as pd df_list = pd.read_csv( './list.csv' , sep= ',' ) univ = [] weights = {} for i, v in df_list.iterrows(): edge = (v[ 'from' ], v[ 'to' ]) univ.append(edge) weights[edge] = v[ 'dist' ] gs.set_universe(univ) 例えば東京から池袋までの最長経路を求め、その距離を求めるには以下のように書けばよいです。 paths = gs.paths( '東京' , '池袋' ) max_path = next (paths.max_iter(weights)) distance = 0 for edge in max_path: distance += weights[edge] print (distance) あとは出発駅と到着駅について考えられるすべてのパターンを試し、2駅の最長の経路が最も長かったものが求める最長片道経路です。 駅名が異なるが乗り換え可能な場合の扱い ここからは単純に路線を読み込むだけでは処理できないケースについて見ていきます。 まず、東京メトロでは赤坂見附駅と永田町駅のように、駅名が異なる場合でも改札を出ることなく乗り換えが可能な駅が存在します。 Graphillionでユニバースを生成する際には駅名が同じであれば同一のノードとしてみなされますが、駅名が異なる場合は乗り換え可能であるにも関わらずエッジでつながらないことになります。 そこで、これらの駅は駅間営業キロ0kmの線路でつながっているとみなし、重み0のエッジとして実装しました。 from to dist line 赤坂見附 永田町 0 乗り換え 溜池山王 国会議事堂前 0 乗り換え … … … … ある組の駅の間に2路線存在する場合の扱い さらに東京メトロでは2路線が並行している区間がいくつかあります。有楽町線と副都心線は小竹向原以北において線路を共有していますし、細かいところでは霞ヶ関-日比谷などが該当します。 Graphillionはあるノードの組に対しては1つのエッジでしかつなぐことができません。そこで、該当する駅はノード名を (路線名)駅名 とし、別々のノードとした上で「駅名が異なるが乗り換え可能な駅の扱い」と同様に重み0のエッジでつなぎました。 from to dist line (銀座)渋谷 (銀座)表参道 13 銀座線 (銀座)表参道 外苑前 7 銀座線 … … … … (半蔵門)渋谷 (半蔵門)表参道 13 半蔵門線 (半蔵門)表参道 青山一丁目 14 半蔵門線 … … … … (銀座)表参道 (半蔵門)表参道 0 乗り換え 注:当初渋谷にて銀座線と半蔵門線の改札内乗り換えが可能としていましたが、誤りのため訂正いたします。それに伴って上の例を表参道に変更しました。なお、副都心線と半蔵門線は改札内で乗り換えが可能です。 改札外乗り換えになる場合の扱い 東京メトロでの最長経路問題を複雑にしているのが改札外での乗り換えです。池袋駅や大手町駅には、乗り換えのために一度改札外に出るためのオレンジ色の改札があります。それに関して旅客営業規程では以下の条件を定めています。 第83条 前条第1項の規定にかかわらず、次に掲げる乗換駅においては、括弧内の路線相互について乗車する発着駅間の普通旅客運賃と比較して、発駅又は着駅から当該乗換駅までの運賃が高額となる場合は、別表第2号表に掲げる運賃による当該乗換駅での乗換えはできない つまり、 すぐ近くまでの切符を買って、オレンジ色の改札がある遠くの駅で改札を出てしまうことはできない ということです。この規定は特に初乗り運賃での最長経路を求めようとするタイプの問題において重要になります。 このような改札外での乗り換えが必要な駅は東京メトロのサイトで案内されている以下の13駅です。( https://ssl.tokyometro.jp/support/faq_answer?lang=ja&faqno=OpenFAQ-000204 ) ただし、旅客営業規程第83条 第3項にもあるように、実際は大手町と新宿三丁目は改札内で乗り換えが可能です。 上野駅(銀座線と日比谷線) 三越前駅(銀座線と半蔵門線) 上野広小路駅‐仲御徒町駅(銀座線と日比谷線) 大手町駅(丸ノ内線と東西線・千代田線・半蔵門線/東西線と丸ノ内線・千代田線・半蔵門線/千代田線と丸ノ内線・東西線・半蔵門線/半蔵門線と丸ノ内線・東西線・千代田線) 池袋駅(丸ノ内線・副都心線と有楽町線) 飯田橋駅(東西線と有楽町線・南北線) 九段下駅(東西線と半蔵門線) 日比谷駅‐有楽町駅(日比谷線・千代田線と有楽町線) 淡路町駅‐新御茶ノ水駅(丸ノ内線と千代田線) 渋谷駅(銀座線と副都心線) 新宿三丁目駅(丸ノ内線と副都心線) 人形町駅‐水天宮前駅(日比谷線と半蔵門線) 築地駅‐新富町駅(日比谷線と有楽町線) 今回はオレンジ色の改札を通らなければならない乗り換え方法については認めないというルールにしたので、「ある組の駅の間に2路線存在する場合」「ある組の駅の間に2路線存在する場合」から、該当する乗り換え表現エッジを削除しました。 2回同じ駅を通る場合を除く 最後に2回同じ駅を通ってしまう場合を排除します。「ある駅の組の間に2路線存在する場合」においてノードの重複を避けるために路線名を頭につけた駅名を採用しましたが、このままでは同じ駅を別の路線で2回通ることが可能になってしまいます。 そこで、先に求めた経路から同じ駅を2回通る場合を引いて、残った経路の集合から最長片道経路を求めます。 例えば、有楽町線の千川と副都心線の千川をどちらも通る場合を考えます。それぞれは including を指定することで抽出できるため、それらの積を全体から引けばよいことが分かります。 senkawa = paths.including( '(有楽町)千川' ).including( '(副都心)千川' ) paths -= senkawa この操作を該当する他の駅に対しても実行することで、2回同じ駅を通過する経路を除くことができます。ただし、「ある組の駅の間に2路線存在する場合」のように (路線名)駅名 の形でノードを作った駅で乗り換えられるように、 ((路線名A)駅名, (路線名B)駅名) の形のエッジが経路に含まれる場合を除いておく必要があります。 注:当初記載されていた情報では方法の説明が不十分であったため、但し書きを追加しました。 これが東京メトロ改札内最長片道経路だ! 和光市から西船橋まで78.8kmの経路です。都内南西でトリッキーな動きをするのが面白いですね。 注:更新に伴って求めた経路を路線ごとに色分けしました。 まとめ 今回はグラフ検索ライブラリGraphillionを用いて、東京メトロの改札内乗り換え最長片道経路を計算してみました。ただし、旅客営業規定の解釈とアルゴリズムの正確性について100%の自信があるわけではございませんので、実際にこの経路で旅行される際は自己責任でお願いいたします。ちなみに、 私なら自分が間違っているのが怖いので必ず1日乗車券を買って行きます 。 もちろん、このような限界に挑むような経路の他にも、「旅行に行くけど目的地をどうやったら効率よく回れるかな」といったカジュアルな問題にもGraphillionは適用することができます。非力なラップトップでも数秒〜数分で経路を出せる素晴らしいライブラリですので、これからはGraphillionとも一緒に旅行するのはいかがでしょうか? 次は卒業生の @lycoris102 さんです。よろしくお願いします!
アバター
こんにちは、 モバイルファクトリー Advent Calendar 2018 - Qiita の19日目担当の id:nesh です。 今日の記事は AppiumでAndroidアプリの自動テストをPerlで書いてみた事についてです。 はじめに この記事では、 headless Chromeでお手軽にWebページのE2Eテスト - Mobile Factory Tech Blog で紹介されたE2Eテストを、モバイルアプリで実行する方法を紹介します。 モバイルアプリ向けの自動テストツールは様々あるのですが、Appiumはその中の一つです。 Appiumの公式サイトの紹介記事 に書いてあるように、 Appiumとは、ネイティブ型及びハイブリット型のモバイルアプリ向けに開発されたテスト自動化を実現するためのフレームワークです。 Appiumを使用する事でiOS、Android端末上でテストプログラムを実行し、GUIテストを自動化する事ができます。 今回は、Appiumを使うための準備を紹介し、業務で特によく使うPerlでテストを書いてみたことを紹介します。 この記事で最終的には Androidアプリの起動をPerlで自動テストできるようにします。 事前準備 必要なもののインストール Appium のインストール npm install -g appium Appiumを使うためのPerlモジュールのインストール cpanm Appium Appium用の設定が正しく入ってるかをチェックするためのツールも入れる Appiumサーバーを起動し、アプリの自動操作を行う時に必要な環境変数などのチェックをしてくれる インストール後にチェックさせて、必要な設定を行う npm install -g appium-doctor Androidのサンプルアプリを用意 アプリの起動をテストするため、今回は簡単なテキストを表示するだけのアプリを用意します。 以下のアプリはAndroid Studioのエミュレーターを使って、起動した時の画像です。 Hello World アプリ Appiumでアプリが起動するかをテストする 実際にPerlでテストを書きます。 次のコードは、指定したエミューレーターで該当アプリを起動できるかどうかをチェックします。 use Appium; use Test::More; my %desired_capabilities = ( app => ${ ビルドしたapkのファイルパス } , deviceName => 'emulator-5554' , # `adb devices` を使って、起動したエミュレータ名を確認する platformName => 'Android' , platformVersion => '8.1.0' , ); my $appium = Appium->new( desired_capabilities => { %desired_capabilities }); ok $appium , 'アプリを起動できる' ; $appium->quit ; done_testing(); %desired_capabilities でテスト対象のアプリとエミュレーターの情報を設定する Appium->new() でAppiumサーバー経由でエミュレーター上に対象アプリを起動させる 対象アプリが起動できたら、返り値の $appium が defined になる テストの最後に $appium->quit でテストアプリへの接続を切り、アプリを終了させる 上記のコードを appium.pl で保存し、次のようにテストを実行します。 Android Studio のエミュレータを起動する $ adb devices List of devices attached emulator-5554 device Appiumサーバーを起動する $ appium [Appium] Welcome to Appium v 1.10 . 0 [Appium] Appium REST http interface listener started on 0.0 . 0.0 : 4723 テストファイルを実行する この時、エミュレーターの様子を確認しながらテストを実行すると、アプリが自動で起動できたことが見れます。 $ perl appium.pl ok 1 - アプリを起動できる 1 .. 1 まとめ ここまで、Androidアプリの自動テストをPerlで書く時の方法を簡単に紹介しました。 iOSアプリ用の自動テストも書けますが、別の機会で試したいと思います。 明日は @PikkamanV さんが担当します。楽しみにしてます!
アバター
こんにちは。 今回アドベントカレンダー初参加な @QBMK_IQU と言います。 現在モバイルファクトリーでサーバーサイドエンジニアとして働いています。 これは モバイルファクトリー Advent Calendar 2018 18日目の記事です。 昨日は @mizuki_r さんの モバイルファクトリー在籍中に開発したOSSの振り返り でした。 はじめに モバイルファクトリーは位置情報を使ったアプリである、ステーションメモリーズ!(通称駅メモ!)を提供しております。 僕個人的にはUnityに興味があり勉強会に参加したり、趣味で実装したりしています。 では、Unityで地図を表示するにはどうすればいいのか?と思ったので実装してみます。 また、Unityには多くのアセットが販売されていることや、Unityに対応したSDKもあることも魅力なので、地図系のアセットやSDKの紹介もしようと考えています。 Unityで地図を表示する実装 今回はGoogle Mapを利用しようと思います。 今回の目的としては単純にとある地点を中心とした地図を表示したいだけなので、方針としては、 Google Static Maps API を利用して地図画像を取得し、取得した画像をUnity上のオブジェクトに貼り付ける方針で実装してみます。 APIキーを取得する Google Static Maps APIが含まれるGoogle Maps APIを利用するためにはAPIキーが必要です。 ですので、まずはAPIキーを取得します。 Google Maps Platform にアクセスします。 Googleアカウントでログインし、右上の「スタートガイド」か「使ってみる」をクリックします。 どのAPIを有効にするかチェックする所があります。今回は全部チェックをいれました。 「+Create a new project」を選択し、分かりやすい名前をつけます。 請求先情報を登録していきます。 請求先情報が登録されると、APIの有効化に移ります。 「次へ」をクリックします。 これでAPIを取得できました。 使用料に関しては 価格表 で確認することができます。 今回使っているGoogle Static Maps APIは月10万回の表示までは無料です。 個人や研究目的くらいの使用量ならば、ほぼ超えることはないと思うので安心ですね。 地図を取得し貼り付ける実装 まずは、貼り付けるimageを用意します。 ヒエラルキー上で右クリックし、「UI」→「Image」で追加します。 次にスクリプトの用意です。 以下のようなスクリプトを追加します。 using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.UI; public class DrawGoogleMap : MonoBehaviour { public float lat = 35.6259034f ; public float lng = 139.7268499f ; public string key = null; public int zoom = 15 ; // Google Maps Embed API string Url = @ "https://maps.googleapis.com/maps/api/staticmap?" ; // Use this for initialization void Start () { Build (); } // Update is called once per frame void Update () { } public void Build (){ // 中心座標 Url += "center=" + lat + "," + lng; // ズームレベル Url += "&zoom=" + zoom; // 地図画像のサイズ Url += "&size=480x480" ; if (key != null && key.Length != 0 ) { Url += "&key=" + key; } Url = Uri. EscapeUriString (Url); StartCoroutine ( Download (this.Url, tex => addSplatPrototype (tex))); } /// GoogleMapsAPIから地図画像をダウンロードする IEnumerator Download (string url, Action<Texture2D> callback) { var www = new WWW (url); yield return www; // Wait for download to complete callback (www.texture); } /// imageにテクスチャを貼り付ける public void addSplatPrototype (Texture2D tex) { GetComponent<Image> ().sprite = Sprite. Create (tex, new Rect ( 0 , 0 ,tex.width,tex.height), Vector2.zero); } } このスクリプトを、先程追加したimageのコンポーネントに追加します。 そして、keyの部分に取得したAPIキーを入力します。 動作 再生ボタンを押して実行すると、上記の画像のように地図を表示することができました。 地図系のアセットやSDKの紹介 Unityのアセットストアでは数多くのアセットを売っており、それらを利用することで開発者は表現の幅を広げたり、開発工数の短縮を行うことができます。 また、Unity自体がメジャーになってきたので、様々なサービスがUnity用のAPIやSDKを提供している場合もあります。 ここでは、そういったSDKやアセットの中から地図に関連したものを紹介していきます。 Google Maps Platform cloud.google.com Googleは2018年3月に、ゲーム開発者に向けた「Google Maps API」での「Googleマップ」のリアルタイムデータの提供とUnityのゲームエンジンとの統合を発表しています。 Googleマップ上の建造物、道路、公園などのデータをUnityのGameObjectsとして使え、開発者はこれらのデータにテクスチャやスタイルを追加して使うことで、ゼロからゲームの世界を設計せずに済むそうです。 こちらのゲームへのサービスは、今回使ったAPIとは別軸の有料サービスとなっていて、詳しくは上記URLから問い合わせる必要があります。 MapBox for unity www.mapbox.com MapBoxではただ地図を表示するだけでなく、Mapbox Studioを使って表示のスタイルを細かくカスタマイズできたり、3Dの町並みを作ってUnityで利用できたりします。 こちらはMAU5万までの無料プランからアクセス数に応じた有償プランがあります。 NAVITIME Map SDK for Unity api-sdk.navitime.co.jp こちらはゲームアプリで地図を使った画面を作成することのできるSDKです。 Unityに対応したSDKもあり、ゲーム向けにデフォルメされたテクスチャや2D、3D、航空写真表示など用途に合わせた日本国内の地図表示を行うことができます。 詳しくは問い合わせる必要があるようです。 WRLD Unity SDK www.wrld3d.com こちらは地形生成系のアセットです。 地図データをAPIで取得し、Unity上でMeshを生成してゲームやアプリなどで利用することができます。 こちらはMAU6万までの無料プランからアクセス数に応じた有償プランがあります。 GoMap assetstore.unity.com こちらも地形生成系のアセットです。 特定の地点およびその周辺の地形を生成する事ができます。 最後に 今回行った地図を表示するという一つの事を行うにしても、APIを使って自分で実装する、提供されているSDKを使う、販売されているアセットを使うなど様々な方法があります。 今回は既に提供されているGoogle Static Maps APIを利用しました。 また、SDKの提供・更新の終了やアセットストアから消える可能性、コストや機能など、目的やリスクを考えて企業のサービスとして地図を使いたいという場合は自前で地図サーバーやAPIを用意するという判断もアリだと思います。 Unityでは一つの機能を実装するにも様々な方法があります。 実現したいことの規模や必要な機能や運用期間や更新頻度やコストなど、状況にあった方法をいろいろと探してみるのも楽しいかと思います。 次は id:nesh です。お楽しみに!!
アバター
これは、 モバイルファクトリー Advent Calendar 2018 の16日目の記事です。 こんにちは、エンジニアの id:tenmihi です。 今日の記事は、vue-routerのナビゲーションガードを利用してページ遷移時のローディング処理を実装する方法のご紹介です。 概要 業務で次のような要件を満たすフォームをvueを用いて実装しました。 フォームが複数のページで構成されている(いわゆるウィザード、ステップ形式) 各ページに遷移するタイミングでフォームの構成に必要なデータをfetchしてきて、結果が返るまではフォームの操作をローディング画面を挟んで制限する ローディング処理についてcreatedフック内のfetch処理の前後でloadingフラグを操作する方法を考えましたが、毎ページでフラグ操作を記述するのが冗長に感じました。 < template > < div > < form > ... </ form > </ div > </ template > < script > export default { ... created () { // loadingフラグを元に上位のcomponentでローディング画面を表示させる commit ( 'setIsLoading' , true ) fetch () commit ( 'setIsLoading' , false ) } } </ script > 理想としてはfetchだけをcreatedフックあるいは相当の処理内で行い、loadingフラグは別でよしなに操作されてほしいです。 そこで今回は、vue-routerのナビゲーションガードを利用してページ遷移時のローディング処理をまとめてみました。 ナビゲーションガード ナビゲーションガード とはvue-routerの機能の1つです。 // 例 async beforeRouteEnter(to, from, next) { await fetchHoge(); next() } ガードはページの遷移前や後にフックされ、そのタイミングで任意の処理が可能です。 また、同期的な処理も可能でapiの結果によって遷移をキャンセルするようなことも可能となっています。 vue-routerに対するグローバルガードだけでなく、各コンポーネントに対して個別にガード処理を実装することができます。 利用したモジュール vue@2.5.21 vue-router@3.0.2 vuex@3.0.1 実装 注意 vue-cli(3.2.1)で作成したプロジェクトで実装しています 単一ファイルコンポーネント(.vue)で書くことを想定しています vue, vue-router, vuex の利用方法については触れません App.vue < template > < div id = "app" > < div id = "nav" > < router- link to= "/" > Hoge </ router- link > | < router- link to= "/piyo" > Piyo </ router- link > </ div > < span v-show= "is_loading" > loading... </ span > < router-view v-show= "!is_loading" /> </ div > </ template > < script > export default { computed: { is_loading () { return this .$store.state.is_loading } } } </ script > Appコンポーネントはrouter-viewを持っていて、パスによって表示する中身を変えます。 ただし、state.is_loadingが立っている場合はrouter-viewではなく"loading..."を表示します。 router.js import Vue from 'vue' import Router from 'vue-router' import store from './store' import Hoge from './views/Hoge.vue' import Piyo from './views/Piyo.vue' Vue.use(Router) const router = new Router( { routes: [ { path: '/' , name: 'hoge' , component: Hoge } , { path: '/piyo' , name: 'piyo' , component: Piyo, } ] } ) router.beforeEach((to, from, next) => { store.commit( 'setIsLoading' , true ) next() } ) router.afterEach(() => { store.commit( 'setIsLoading' , false ) } ) export default router パスが / のときHogeコンポーネントが、 /piyo のときPiyoコンポーネントが表示されるようなルーティングとなっています。 router.beforeEachとrouter.afterEachはグローバルガードと呼ばれ、どのページでも呼ばれます。 ここではページ遷移前(beforeEach)にis_loadingフラグを立てて、ナビゲーションガードが解決されて遷移する直前(afterEach)にis_loadingフラグを下ろしています。 store.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)) const store = new Vuex.Store( { state: { message: '' } , mutations: { setMessage(state, message) { state.message = message } } , actions: { async fetch( { commit } ) { await sleep(1000) store.commit( 'setMessage' , 'piyo' ) } } } ) export default store messageとis_loadingをstateとして持ち、1秒待って'piyo'をmessageにsetするようなfetch actionが実装されています。 今回はこのfetch actionをページ遷移前に叩くapiに例えて利用します。 views/Hoge.vue < template > < div > < h1 > hoge </ h1 > </ div > </ template > Hogeコンポーネントは単にhogeを表示するだけのシンプルなコンポーネントです。 views/Piyo.vue < template > < div > < h1 > {{ message }} </ h1 > </ div > </ template > < script > import store from '../store' export default { data () { return { message: this .$store.state.message } } , async beforeRouteEnter ( to, from, next ) { await store.dispatch ( 'fetch' ) next () } } </ script > Piyoコンポーネントはdata初期化時にstate.messageと同値になるmessageをバインドしています。 ナビゲーションガードであるbeforeRouteEnterメソッドを実装しており、ここで先程のfetch actionを同期的にdispatchします。 ここではfetch actionのdispatchが終わった後にnextメソッドを呼んでページを遷移させるようにしています。 動作 /hoge から /piyo に遷移してみます。 ページ遷移時の動作 リンクをクリックしてからloadingが表示されて1秒経った後にpiyoが表示されています。 パスを見てもfetchが終わった後にpiyoページへ遷移していることが分かりますね。 注意点 当然ですが、同期的なデータ取得となりapiを叩いてレスポンスが帰ってくる時間だけページ遷移に時間がかかるようになるので、結果UXとしては悪くなります。 今回のように遷移が遅いと実感できるような処理を挟む場合は、非同期的なデータ取得で実現できないかを考えたほうが良いです。 まとめ ナビゲーションガードを利用することで、ページ遷移時のローディング処理をまとめることが出来ました。 今回例に上げた以外にも、ページ遷移前に認証済みかどうかをチェックしてリダイレクトさせたりとナビゲーションガードは様々な用途で利用できそうです。 明日は mizuki_r さんです!
アバター
こんにちは、フロントエンド寄りのエンジニアの @1derful です。 2018年モバイルファクトリーアドベントカレンダー 15日目の記事です。 前日は mattak さんの「 フリーランスになって変化したこと 」でした。 はじめに フロントエンド分野の技術は移り変わりが激しいですね。 とはいえ、流行の速いトレンドもあれど、W3Cが策定したWeb標準技術はメンテナンスが止まらない限り使い続けられます。 そこで、 Vue.js や TypeScript などの新しい技術に FileReader と IndexedDB の古くからあるWeb APIを織り交ぜて使ってみたいと思います。 まず開発環境を作ります 今回は Vue CLI を使います。 Vue.js の迅速な開発環境構築のため、TypeScript から JavaScript に変換するトランスパイラや、各モジュールを束ねるバンドラのインストールを一括で行い、雛形をスキャフォールディングしてくれるツール群です。 また、TypeScript を公式サポートしていますので Vue CLI の設定時に TypeScript 使用するよう選択します。 Vue CLIでの今回の記事の設定 それでは、 Vue CLI をインストール Dexie.js をインストール PapaParse をインストール PapaParse の型定義ファイルをインストール *1 プロジェクトを作る の手順で行います。 ちなみに、IndexedDB はクライアントサイドで使える NoSQL のデータベースです。 IndexedDB API をそのまま使うと複雑な作りになるのでシンプルに扱える Dexie.js を使うことにします。他にも IndexedDB のライブラリはあるので、使い比べてみるのがよいと思います。 そして、CSV のパースを自力で行うこともできますが、CSVライブラリがあるので楽するために使いましょう。 そこでメンテナンスが継続されていて、使用方法が簡単で思った形に整形しやすい PapaParse を使うことにしました。 それでは、以下のコマンドでインストールしてプロジェクトを立ち上げます。 yarn add @vue/cli --dev yarn add dexie --dev yarn add papaparse --dev yarn add @types/papaparse --dev vue create csv-hangar vue serve 結果、以下のバージョンの環境が作れました。 node: 11.4.0 vue-cli: 3.2.1 typescript: 3.2.2 実際にコードを書く <!-- Bootstrapを使ってます --> < template > < div class = 'excel-uploader' > < div class = 'input-group-btn' > < label class = 'btn btn-primary' > アップロード < input type = 'file' accept = '.csv,text/csv' @change= 'handleUpload' class = 'file-upload' > </ label > </ div > </ div > </ template > 味気ないのでロゴを付けてみました それでは Dexie を使って IndexedDB にデータベースを作っていきます。 @Component export default class CSVUploader extends Vue { /** data: メンバ変数 */ public csv: string = '' ; public db: AppDatabase = new Dexie( 'AppDatabase' ) as AppDatabase; /** Vueのライフサイクルフック */ public created(): void { /** テーブル定義 */ this .db.version(1).stores( { customer: '++id' , // auto_increment } ); /** デフォルトとしてダミーのリストをIndexedDBに追加 */ this .db.customer.bulkAdd( [ { name : '名無しの権兵衛' , age: 67, email: 'foo@foo.foo' } , { name : 'ジョン・ドウ' , age: 32, email: 'bar@bar.bar' } , ] ); } ここで Chrome をお使いならば Developer Tool を開きます。 Developer Tool の Application タブ から IndexedDB を見てみると、命名指定した「AppDatabase」が作られているのがわかります。 無事に「customer」テーブルも作成されているのが確認できました。 ブラウザ立ち上げ時にダミーが入ります 以下でアップロード時の処理を追加していきます。 /** methods: CSVアップロード時の処理 */ public handleUpload(ev: HTMLElementEvent<HTMLInputElement>): void { const files = ((ev.target as any) as HTMLInputElement).files!; if (!files.length) { return ; } this .createCSV(files [ 0 ] ); } /** methods: CSVをパース */ public createCSV(file: File): void { const reader = new FileReader(); const vm = this ; reader.onload = (ev: ProgressEvent): void => { const csv: string = (ev.target as any).result; const collection: any = Papa.parse(csv, { header: true } ); vm.addRecord(collection); } ; reader.readAsText(file); } /** methods: IndexdDBに追加 */ public addRecord(collection: any): void { this .db.customer.bulkAdd(collection.data); } } 以下のようなCSVファイルをアップロードしてみます。 name,age,email 山田太郎,35,hoge@hoge.hoge ジョン・スミス,42,fuga@fuga.fuga イワン・イワノヴィッチ・イワノフ,21,piyo@piyo.piyo アップロード後に再びDeveloper Toolを開きます。 更新されていない場合は、Developer Toolを再起動するといいです。 CSVのレコードが追加されているのがわかります 型定義ファイル TypeScript で作る際は、以下の型定義ファイルをインポートする必要があります。 import { Component, Vue } from 'vue-property-decorator' ; import { AppDatabase, HTMLElementEvent } from '../libs/definitions' ; 前者は Vue.js 上で TypeScript を使うのに必ず必要なデコレータです。 Vue CLI のインストールで TypeScript を選択すると、自動でインストールされます。 後者は、自作するしかないので以下に内容を示します。 /** IndexedDB */ import Dexie from 'dexie' ; export type DexieDatabase = {[ P in keyof Dexie ] : Dexie [ P ]} ; /** データベースの型定義 */ export interface AppDatabase extends DexieDatabase { customer: Dexie.Table<ICustomer, number>; } /** テーブルの型定義 */ export interface ICustomer { id?: number; name: string; age: number; email: string; } /** Event */ export interface HTMLElementEvent<T extends HTMLElement> extends Event { target: T; } TypeScript: Documentation - TypeScript 2.1 等を参考にしています。 HTMLElementEvent は定義されておらず TypeScript のコンパイラでコケますので定義します。 それでは駆け足でしたが、お元気で! 明日は tenmihi です。お楽しみに!! *1 : TypeScript のコンパイラに型を教えてあげないとビルドできないので、型定義ファイルが必要になります。
アバター
モバイルファクトリー Advent Calendar 2018 、13日目担当の htk291 です。 昨日は @return520 さんの PerlでTwitterのPremium Search APIを叩く でした。 はじめに 2018年初あたりから社内のプロダクトには1byteもコードを書いていませんが、 コードを書く以外のエンジニアのキャリアとしてプロダクトマネージャに興味をもち、社内外で名乗っています。 本日は新規プロダクトにプロダクトマネージャとして関わった際の気付き、学びについてお話をします。 論理よりも信頼と共感 「人」に関する学び ロゴスだけじゃなくて、エトスとパトスも大事な要素だよ by アリストテレス チームに物事を共有するときには論理的な正しさも必要ですが、それが共感できるか、しっかりと意義や目的が共有されて理解されているかが鍵となります。 例えばよくある話として、彫刻家に「こんな像を彫ってほしい。それが終わったら次はこんな像をお願い」と説明するよりも、「私達はこの街に世界一の大聖堂を作ろうとしている。あなたにはファサード部分の彫刻を彫ってほしい。非常に重要な仕事であなたにしかお願いできない」というように、我々チームは何を作ろうとして、その仕事にはどんな意義があるのか、任される仕事はどんな意味があるのかを説明し、共感を得ることが重要です。 実際にチームでは私の説明不足により、メンバーそれぞれで思い描くゴールが異なってしまっていたり、この作業に意味はあるのか等の疑問を生んでしまうこととなりました。 そのような疑問が生じる前に、まずは意義や目的を伝えることで共感を得て、同じゴールに向かおうとしていることを確認してから詳細な仕様の説明に入ることが大事です。 ぼくのかんがえたさいきょうのあぷり 「製品」に関する学び 製品開発というのは、自分が思いついた最新のアイデアや 最高のアイデアを試す場ではないことを忘れてはならない 『INSPIRED: 顧客の心を捉える製品の創り方』 私もそうでしたが、経験の浅いプロジェクトマネージャやチームは、自分達が最もユーザの気持ちを理解していて、自分達が定義したこの仕様なら必ず売れる!といった錯覚をしてしまいがちです。 開発現場で定義した仕様が、本当にターゲットの望むものかどうか。現場が定義するものは仮説でしかありません。早期に検証して、ターゲットと思われるユーザからのフィードバックを得ることが思いもよらないユーザの声を聞けるチャンスとなります。 「開発中のプロダクトを安易に外部に公開できない」という声が聞こえてきそうですが、チームからちょっと離れたところに居るターゲットにヒアリングするだけでも非常に多くのフィードバックを得られることがあります。 社内にいる別チームの方に協力をお願いするだけでも製品にとって有意義な情報となるでしょう。 課題の発見と解決アプローチの選択 「プロセス」に関する学び それってなんの課題を解決するための機能なの? by 弊社シニアPdM 課題のないところに解決策はありません。 ですが、プロダクトマネージャや開発チームは「良さそうな機能」を思いつくと製品に取り入れたくなるものです。 そんな場合は、一旦立ち止まって考えることが大事です。 その機能はユーザのどんな課題を解決するための機能ですか? これが思いつかないのであれば、きっとその機能は「良さそう」なだけであって使われない機能になってしまうでしょう。 私は「良さそう」というだけで思いついた機能を入れたいと思ってしまい、当時のシニアPdMの方から「何を解決する機能なの?その解決になぜこの手段を選択したの?」と何度も問われ考え直す経験をしました。 まとめ エンジニアからプロダクトマネージャに立場が変わり、次のような気付きがありました。 チームには目的や意義を伝える ユーザの声を聞き、独りよがりにならない 解決したい課題のない機能は、必要とされない 現在、コードを書くという業務からは離れていますが、私はプロダクトマネージャとなる選択をして間違っていなかったと考えています。 明日は卒業生の mattak さんです!!
アバター
こんにちは、エンジニアの @return520 です。 これは 2018年モバイルファクトリーアドベントカレンダー 12日目の記事です。前日は @toricor_で「headless Chromeでお手軽にWebページのE2Eテスト」でした。 はじめに 本記事では、Twitter のツイート検索APIのPremium Search APIを、Perlで利用する方法を紹介します。 Twitterは、ツイートの検索APIを複数提供しており、Premium Search APIでは、30日前まで遡れる 30-day と、全ての期間を遡れる Full-archive でツイートを検索することができます。 ※通常のStandard Search API では7日間までのツイートしか遡れません。 Overview | Docs | Twitter Developer Platform There are two premium search API endpoints: Search Tweets: 30-day endpoint → provides Tweets posted within the last 30 days. Search Tweets: Full-archive endpoint → provides Tweets from as early as 2006, starting with the first Tweet posted in March 2006. 今回は OAuth::Lite を使用してTwitterのPremium Search APIを叩いてみようと思います。 ※Premium Search APIの登録方法などは説明しません。 APIを叩く前に エンドポイントの確認方法 Premium Search APIでツイートを取得したい場合は、Standard Search APIとは異る、以下のエンドポイントを使用します。 GET https://api.twitter.com/1.1/tweets/search/:product/:label.json :product には、 30day もしくは fullarchive のどちらかを入れます。 今回は fullarchive で試してみようと思います。 :label には https://developer.twitter.com/en/account/environments で setupする際に自身でつけた Dev environment label をそのまま入れます。 今回は dev とい名前にしましたので、 dev.json になります。 結果、エンドポイントは GET https://api.twitter.com/1.1/tweets/search/fullarchive/dev.json になりました。 エンドポイントポイントを叩く前の注意 Search API | Twitter API | Docs | Twitter Developer Platform Premium Search APIには、Sandbox と Premium が存在します。 Sandboxは無料で利用することができますが、Premiumは有料です。 ちなみに、Sandboxでは一ヶ月間でAPIを叩ける回数に制限があります。 30day 250回まで fullarchive 50回まで Premium もプランによって制限があります。ダッシュボード残り回数を確認することができるので注意しましょう。 https://developer.twitter.com/en/dashboard OAuth::LiteでPremium Search APIを叩く OAuth::Lite::Consumerを作成する まずは、OAuth::Lite::Consumerを作成します。 consumer_key consumer_secret access_token access_token_secret については、Twitter Applicationを作成して取得をしてください。 use OAuth::Lite::Consumer; sub create_consumer { my $consumer_key = 'hoge' ; my $consumer_secret = 'hoge' ; my $access_token = 'hoge' ; my $access_token_secret = 'hoge' ; return OAuth::Lite::Consumer->new( consumer_key => $consumer_key , consumer_secret => $consumer_secret , access_token => $access_token , access_token_secret => $access_token_secret , ); } APIのエンドポイントを叩く 次に、作成したconsumerでAPIを叩くことができます。 公式ドキュメントにparamsの説明はありますが、以下のようなものが扱えます。 query fromDate toDate next fromDateとtoDateはUTCになりますので、注意してください。 nextは後に説明します。 以下のコードでは、 2018 10/26 00:00〜2018 10/27 00:00 に投稿された #Perl というハッシュタグがついたツイートを を検索しています。 use Encode; use OAuth::Lite::Consumer; sub create_consumer { my $consumer_key = 'hoge' ; my $consumer_secret = 'hoge' ; my $access_token = 'hoge' ; my $access_token_secret = 'hoge' ; return OAuth::Lite::Consumer->new( consumer_key => $consumer_key , consumer_secret => $consumer_secret , access_token => $access_token , access_token_secret => $access_token_secret , ); } sub request { my $consumer = create_consumer(); my $query = '#Perl' ; # UTC my $from_date = '2018102515:00' ; # 2018 10/26 00:00 my $to_date = '2018102615:00' ; # 2018 10/27 00:00 return $consumer->request ( method => "GET" , url => "https://api.twitter.com/1.1/tweets/search/fullarchive/dev.json" , params => +{ query => encode( 'utf-8' , $query ), fromDate => encode( 'utf-8' , $from_date ), toDate => encode( 'utf-8' , $to_date ), }, ); } レスポンスをjsonに変換する 取得したレスポンス( $res )は HTTP::Response のObjectになります。 $res->content で生のcontent、もしくは、 $res-> decoded_content でデコードした後のcontentを取得することができます。 また、正常にリクエストできたかどうか $res->is_success で判定することもできます。 そして今回は、取得したJSON stringを JSON::XS を用いてdecodeしました。リクエストに失敗した場合はそのmessageを表示させています。 use Encode; use OAuth::Lite::Consumer; use JSON::XS; sub create_consumer { my $consumer_key = 'hoge' ; my $consumer_secret = 'hoge' ; my $access_token = 'hoge' ; my $access_token_secret = 'hoge' ; return OAuth::Lite::Consumer->new( consumer_key => $consumer_key , consumer_secret => $consumer_secret , access_token => $access_token , access_token_secret => $access_token_secret , ); } sub request { my $consumer = create_consumer(); my $query = '#Perl' ; # UTC my $from_date = '2018102515:00' ; # 2018 10/26 00:00 my $to_date = '2018102615:00' ; # 2018 10/27 00:00 return $consumer->request ( method => "GET" , url => "https://api.twitter.com/1.1/tweets/search/fullarchive/dev.json" , params => +{ query => encode( 'utf-8' , $query ), fromDate => encode( 'utf-8' , $from_date ), toDate => encode( 'utf-8' , $to_date ), }, ); } sub get_tweets { my $res = request() if ( $res->is_success ) { my $content = $res->decoded_content || $res->content ; return decode_json( $content ); } else { warn $res->message ; warn $res->decoded_content ; return +{}; } } my $tweets = get_tweets() これで $tweetsには以下のようなデータが入ってきます。 { " results ": [ { -- Tweet 501-- } , { -- Tweet 502-- } , ... { -- Tweet 1000-- } ] , " next ":" R2hCDbpBFR6eLXGwiRF1cQ ", " requestParameters ": { " maxResults ": 500 , " fromDate ":" 201101010000 ", " toDate ":" 201201010000 " } } nextについて 次の検索結果を取得したい場合は、nextを渡すことで取得することができます。 nextの値はAPIのリクエスト結果の nextで取得することができます。 sub get_tweets { ... } my $tweets = get_tweets() my $next = $tweets->{ next } ; my $consumer = create_consumer(); my $res2 = $consumer->request ( method => "GET" , url => "https://api.twitter.com/1.1/tweets/search/fullarchive/dev.json" , params => +{ query => encode( 'utf-8' , $query ), fromDate => encode( 'utf-8' , $from_date ), toDate => encode( 'utf-8' , $to_date ), next => encode( 'utf-8' , $next ) }, ); さいごに 今回はPerlでTwitterのPremium Search APIを叩いてみました。 OAuth::Liteの使い方を知っていると、Twitter以外にもdiscordなど他のAPIも簡単に叩けるようになるので、知っていて損はないかと思います。 明日は htk291 です。お楽しみに!
アバター
これは、 モバイルファクトリー Advent Calendar 2018 の11日目の記事です。 こんにちは、 id:toricor です。好きな分野はソフトウェアの品質向上・テスト・自動化です。 今日の記事はブラウザ操作自動化ツールを使い、E2Eテストを小さく試したときのお話です。 要約 ブラウザ操作自動化ツールを使いE2Eテストを実行する方法を紹介します(Chromeのみでの実行になります)。 E2Eテストとは 関数などの部品単位を対象にした単体テストに対して、例えばUIを実際に操作してフロントエンド/サーバサイド/DBのすべてが期待通りに動くことを確かめるようなテストはE2Eテスト(End to Endテスト)と呼ばれます。 部品それぞれが正しく動いても組み合わせたときに全体が正しく動くとは限らないので、組み立てたWebページを対象としたテストで動作を見てあげることが必要です。 例えば今日の記事で紹介するようなブラウザ操作自動化ツールを使用することで、ユーザーがWebサービスを利用するときのような一連のブラウザ操作を再現させ、それをテストとして表現することができます。 headless Chromeとpuppeteer Chromeといえば有名なブラウザですが、headlessモードを使えばGUIなしで高速に動作させる事ができることからE2Eテストで使われています( headless Chrome )。 headless Chromeは開発者が操作しやすいように、DevTools Protocolを介して操作するpuppeteerというNodeのライブラリが提供されており、このライブラリがブラウザ操作を助けてくれます。 github.com puppeteerの特徴を上げると GoogleのDevToolsチームがメンテしてます(コードが長期間メンテされ そう ですよね!) ブラウザ操作でできる大体のことはpuppeteerを介してもできます フォームを埋めたり SPAをクロールしたり デバイスサイズに合わせたスクリーンショットを取ったり テストを動かす puppeteer自体はただのブラウザ自動操作ツールなので、テストとしてチェック項目を確認できるようにテスト用のツールも導入します。 テストフレームワークの選定 js界ではいくつか有名なテストフレームワークがありますが今回は Jest を採用しました。 jsにはまだそれほど慣れていないので ドキュメントが豊富なこと すぐ動かせるサンプルコードがあったこと を基準に選びました。 今回つくったE2EテストはJestの公式ドキュメントにあった サンプル を利用したので基本ケースをすぐに作ることができました。 テストの設定 Jest, puppeteer, jest-puppeteer (puppeteerを使ったテストの設定をいい感じにしてくれる)をインストールします。 __tests__ 以下にテストファイルを配置し、 yarn test でテストを実行するようにします。 "scripts": { "test": "jest __tests__", }, 実際にテストを実行する際には、環境変数の設定などを行う処理などとともにシェルスクリプトに記述しておき、シェルスクリプトを実行するようにしました。 テスト例 例えば「駅メモ!」の公式サイトの 「駅の思い出」ページ をテストします。 「駅の思い出」ページでは検索ボックスが1つあり、 駅名を検索することで、その駅のページに遷移します 駅のページでは駅の情報を表示することができます 今回のテスト例としては、表示される路線情報・都道府県情報が期待通りなのかを確かめます。 const timeout = 5000 ... it( '五反田という駅名で検索できる' , async () => { const stationName = '五反田' await page.type( '.text-input' , stationName) await page.click( '.search-btn' ) await page.waitFor( '.activity-best-matched-station--result' ) let text = await page.evaluate(() => document .body.textContent) // 五反田に乗り入れる各路線が期待通り表示されるかどうか expect(text).toContain( 'JR山手線' ) expect(text).toContain( '東急池上線' ) expect(text).toContain( '都営浅草線' ) // 五反田は東京都の駅である expect(text).toContain( '東京都' ) // 五反田駅のページのスクリーンショットを取得する await page.screenshot( { path: '__tests__/database-gotanda.png' } ) } ) } , timeout ) テスト結果 前処理-テスト-後処理の順で実行し、テストを通過させることができました。 yarn testの実行ログ(成功した場合) またスクリーンショットも以下のように簡単に取得することができました。 五反田駅の検索結果 デバッグ方法 もし意図しない結果を得た場合、下の例のようにheadlessモードをオフにしてテストを実行することでブラウザのウィンドウが立ち上がり、puppeteerが自動で操作する様子を目で見ながら確認することができます。 const browser = await puppeteer.launch( { headless: false , slowMo: 500, // スローモーション } ) 今後の展望 E2Eテストの対象ページを増やしたり、スクリーンショットの差分チェックテストをしたりできるといいですね! 明日は id:narita-cpp です。お楽しみに!
アバター
概要 こんにちは! モバイルファクトリー Advent Calendar 2018 10日目担当の shioiyan です。 9日目の記事は @isaoeka さんの 草ウィジェットを作った🤔 でした。 本記事では @mattak さんが開発された、UnityでReduxのような状態管理が行えるようになる Unidux を使って簡単なゲームサイクルを実装してみます。 この記事を読んでわかること Uniduxを使った画面遷移が実装方法がわかる PageとSceneの紐付け方 PageとSceneの状態管理のやり方 PageとSceneの状態の監視方法 Pageの変更のやり方 Pageへのデータの持たせ方 ※会社で開発しているアプリ/プロジェクトとは関係がありません。 動機 ゲームジャムなどで簡単なゲームを作っていると、いつもタイトル画面->ゲーム画面->リザルト画面->タイトル画面...という簡単な画面遷移を実装するのにそれなりの時間がかかっていることに気がつきます。 タイトル画面やリザルト画面はゲーム制作の本質的な部分ではなく、特に限られた時間で行われるゲームジャムでは、できれば時間をかけずに作りたいところです。 また、ゲームの規模が大きくなるに従い、少ない画面数ならなんとか管理できていたものがだんだん管理しきれなくなっていきます。 画面遷移の仕組みはどんなゲームを作るにしても必要です。 あらかじめ実装方法を確立しておき、ゲーム開発で本質的な部分に注力して開発できるようにしておきたいところです。 解決策 UniduxのSceneTransitionを使えば、拡張性の高い画面遷移が実装できます! やや作業量は多いかもしれませんが、1度実装しておけばあらゆるアプリケーションで使えるはずです。 今回作るもの タイトル画面->ゲーム画面->リザルト画面->タイトル画面...と遷移する ボタンを押すと画面遷移が行われる タイトル画面で選択した難易度がゲーム画面で表示される ゲーム画面から遷移する時にスコアが渡されてリザルト画面でスコアが表示される github.com   開発環境 Unity v2018.2.16f1 Unidux v0.3.4 UniRx v6.2.2   実装 ※ Uniduxの使用例にあるSceneTransitionの実装を参考にしています。 ※ Unidux自体やUniRxの使い方についてはここでは触れません。 PageとSceneの紐付け まずは使用する Sceneを作成 します。 今回は、どのPageにも常に存在するBaseというSceneと、Pageによって切り替わるTitle、Game、Resultの3つのSceneを作成します。 Uniduxでは1つの画面のことをPageと表現します。 Pageは1..n個のSceneの集まりです。 作成したSceneは enumで同じ名前で定義 しておきます。   今回はこれらのSceneを使って3つのPage(タイトル画面、ゲーム画面、リザルト画面)を作るので、こちらも enumで定義 しておきます。 次に SceneConfig.cs というスクリプトを作り、各PageにどのSceneが必要かマッピングしていきます。 SceneCategoryというパラメータがあり、Permanentに設定されたSceneは常に表示されるようになります。 今回は各PageにBaseと対応するそれぞれのSceneが表示されればいいので、CategoryMapでBaseのSceneCategoryをPermanentに設定し、PageMapで各PageとSceneをマッピングします。 もしHeaderやFooterといったSceneが必要ならそれぞれPageに紐付けると良いでしょう。   SceneとPageの状態を管理 ゲームの状態を管理するクラスに SceneStateとPageState を持たせます。   SceneWatcherとPageWatcherの作成 SceneStateとPageStateの変更を監視して実際にUnity上のSceneを変化させる SceneWatcher と PageWatcher を作成します。 ここで使われている SceneUtil でUnityお馴染みの SceneManager.LoadSceneAsync() が実行されています。   Pageを変更する これでSceneStateやPageStateを変更するActionが発行されるとそのActionに応じてSceneやPageが変更されるようになりました。 次にButtonが押されたらPageが変更されるようにしてみます。 例えば、 リザルト画面からタイトル画面に戻るボタンのスクリプト は以下のように実装します。   using GameCycleSample.Config; using Unidux.SceneTransition; using UnityEngine; using UniRx; using UnityEngine.UI; namespace GameCycleSample.UI { [RequireComponent( typeof (Button) )] public class ReturnToTitleButtonDispatcher : MonoBehaviour { void Start() { this .GetComponent<Button>().OnClickAsObservable() .Select(_ => PageDuck<Page, Scene>.ActionCreator.Push(Page.TitlePage)) .Subscribe(action => Unidux.Dispatch(action)) .AddTo( this ); } } } PageDuck<Page, Scene>.ActionCreator.Push(Page.TitlePage) というActionをDispatchすることでタイトル画面に必要なSceneを表示させています。 また、 PageDuck<Page, Scene>.ActionCreator.Pop() というActionも用意されており、前のPageへ戻ることも行えます。   Pageにデータを持たせる UniduxではPageをPushする時にPageにデータを持たせることができます。  PageDuck<Page, Scene>.ActionCreator.Push() の第二引数にIPageDataを継承したクラスを渡すことができます。 今回はタイトル画面で難易度を選択できるようにし、ゲーム画面で選択した難易度が表示されるようにしました。 まずは IPageDataを継承したクラス を実装します。 using System; using GameCycleSample.Type; using Unidux.SceneTransition; namespace GameCycleSample.Struct { [Serializable] public class GamePageData : IPageData { public DifficultyType DifficultyType; // ゲームの難易度 public GamePageData() { } public GamePageData(DifficultyType difficultyType) { this .DifficultyType = difficultyType; } } } タイトル画面で難易度を選択するボタンのスクリプト は以下のようになります。 using GameCycleSample.Config; using GameCycleSample.Struct; using Unidux.SceneTransition; using UnityEngine; using UniRx; using UnityEngine.UI; namespace GameCycleSample.UI { [RequireComponent( typeof (Button) )] public class TitleButtonDispatcher : MonoBehaviour { public GamePageData GamePageData; void Start() { this .GetComponent<Button>().OnClickAsObservable() .Select(_ => PageDuck<Page, Scene>.ActionCreator.Push(Page.GamePage, this .GamePageData)) .Subscribe(action => Unidux.Dispatch(action)) .AddTo( this ); } } }   Inspector上で各ボタンに応じた GamePageData (の DifficultyType )を設定します。 あとは PageDuck<Page, Scene>.ActionCreator.Push(Page.GamePage, this.GamePageData) と渡してあげると PageState.Data に GamePageData が格納されます。    次にPageDataを参照してTextとして表示してみます。 表示したいTextGameObject(今回はTextMeshProUGUIを使っています)に 以下のスクリプト をattachします。 using GameCycleSample.Business; using GameCycleSample.Struct; using TMPro; using UnityEngine; using UniRx; namespace GameCycleSample.UI { [RequireComponent( typeof (TextMeshProUGUI) )] public class DifficultyTypeTextRenderer : MonoBehaviour { public TextMeshProUGUI DifficultyTypeText; void Start() { Unidux.Subject .Where(state => state.Page.IsReady && state.Page.IsStateChanged) .Where(state => state.Page.Data is GamePageData ) // Page.Dataの型チェック .StartWith(Unidux.State) .Subscribe(state => this .Render(state)) .AddTo( this ); } private void Render(State state) { GamePageData pageData = state.Page.GetData<GamePageData>(); this .DifficultyTypeText.text = pageData.DifficultyType.ToString(); } } } state.Page.GetData<GamePageData>() で指定した型のPageDataを取得できます。 GamePageData型のPageDataが存在することを保証するために .Where(state => state.Page.Data is GamePageData) で現在の Page.Data が GamePageData である場合だけ参照するようにしています。 また、 .Where(state => state.Page.IsReady && state.Page.IsStateChanged) とすることで、ゲーム中に難易度が変更されても検知して再描画するようにしています。 同様にして、 ゲーム画面からリザルト画面に遷移する時にスコアを渡して 、 リザルト画面でスコアを表示 するようにしています。   まとめ Uniduxを用いて簡単なゲームサイクルを実装できました。 画面遷移のロジックは実装者によってバラバラな実装になりがちですが、Uniduxを使うことで統一感のある実装になるため、複数人開発する場合にも特におすすめです。 UnityでReduxライクに状態管理が行えるUniduxはとても素晴らしいので、ぜひ皆さんも使っていきましょう!   モバイルファクトリーではゲームジャム部という部活があり、文字通りゲームジャムを開催して自由にゲームを作ったりしています。   モバイルファクトリー Advent Calendar 2018 11日目は id:toricor さんの記事です。 今年もテストの話のようです。楽しみですね!!
アバター
これは モバイルファクトリー Advent Calendar 2018 6日目の記事です。 前日の記事は @koropicot さんの ブロックチェーンの学び方 でした。 こんにちは、新卒ソフトウェアエンジニアの id:mp0liiu です。 吉祥寺.pm#16で「Perlにおけるクラスの実装パターン」というタイトルでLTをしたのですが、 この記事では紹介したクラスの実装パターンのうち、個人的に気に入っている InsideOutオブジェクト(資料でいう18~21P)について詳しく掘り下げてみてみます。 吉祥寺.pm#16で発表した資料はこちらです。 InsideOutテクニックとは?  インスタンスのアドレスを数値として評価し、それをキーとしてパッケージローカルなハッシュ変数などにインスタンス変数を格納することで、完全なカプセル化を実現するクラスの実装パターンです。 Perl Best Practiceで推奨されている方法ですが、あまり普及しませんでした。 メリット インスタンス変数を完全にクラスの外部から隠蔽し、カプセル化を実現する 拡張性がある カプセル化を実現しているクラスの実装パターンのなかでは速度が最も速い デメリット インスタンス変数の呼び出しに比較的時間がかかる 毎回blessしているオブジェクトのアドレスを数値に変換する手間がかかるため(高速化のテクニックもあります) この実装パターンを知らない人にとってはコードが何をやっているのかかなり理解しにくい メモリを開放する処理を自前で実装しなければならない インスタンスが必要なくなってもGCがメモリを開放するのはインスタンスの領域だけで、 各インスタンス変数のデータはパッケージローカルな変数に残り続ける デバッグ、シリアライズがしづらい Hash::Util::FieldHash  クラスビルダーというよりかは、InsideOutテクニックでクラスを作るのをサポートするようなユーティリティを提供するモジュールです。 特徴としては、 インスタンス変数に相当する値へのアクセス、メモリ解放を自動でやってくれる コアモジュール(perlv5.9.4から) コンストラクタやアクセサは手書き このモジュールで提供される機能を用いてクラスを作る場合は、以下のようになります。 package Point { use Hash::Util::FieldHash qw( fieldhash ) ; fieldhash my %x_fields ; fieldhash my %y_fields ; sub new { my ( $class , $x , $y ) = @_ ; my $self = bless \( my $anon ), $class ; $x_fields{$self} = $x ; $y_fields{$self} = $y ; $self ; } sub x { my $self = shift ; $x_fields{$self} ; } sub y { my $self = shift ; $y_fields{$self} ; } } my $p = Point->new( 1 , 2 ); fieldhash 関数で登録したハッシュにインスタンス変数に相当する値を格納しておくと、インスタンスのメモリが開放されるとタイミングで、そのインスタンスのインスタンス変数も自動的に開放されるようになります。 クラスを作る手間はハッシュベースクラスを1からつくるのと同程度になりそうです。 コアモジュールしか使えない環境でInsideOutクラスを作りたいような場合、選択肢に入りそうです。 CPANTSで直接調べられなかったので、CPAN上にあるモジュールでHash::Util::FieldHashに依存しているモジュールの数は正確にはわかりませんでしたが、 perl5.10以前のperlにも対応させたHash::Util::FieldHash::Compatというモジュールに依存しているモジュールの数は、 19個 でした。 Object::InsideOut  Object::InsideOutは非常に多機能なInsideOutクラスのクラスビルダーです。 特徴としては、 メモリ解放を自動でやってくれる コンストラクタ、アクセサの自動生成機能を提供 コンストラクタでの引数チェックの機能を提供 インスタンス変数の型制約をサポート Storableでのシリアライズをサポート インスタンス変数をハッシュでなく配列に格納させることができるので、他のクラスビルダーと比べてインスタンス変数へのアクセスが高速にできる エラー出力が丁寧 attributeを多用する Object::InsideOutでクラスを作る場合は、以下のようになります。 package Point { use Object::InsideOut; my @y_fields : Field # InsideOutオブジェクトのインスタンス変数格納に使用し、この配列に登録されたインスタンス変数の開放処理は自動でされるようになる : Type( 'Numeric' ) # 数値しか受け付けない : Accessor( 'y' ); # 'y' という名前の読み書き可能なアクセサを生成 my @x_fields : Field : Type( 'Numeric' ) : Accessor( 'x' ); # コンストラクタでどのような引数を受け付けるかの設定 my %init_args : InitArgs = ( x => +{ Mandatory => 1 , # 必須の引数 Type => 'Numeric' , # 数値しか受け付けない }, y => +{ Mandatory => 1 , Type => 'Numeric' , }, ); # オブジェクトの初期化 sub init : Init { my ( $self , $args ) = @_ ; $self->set ( \ @x_fields , $args->{ x } ); $self->set ( \ @y_fields , $args->{ y } ); } } my $p = Point->new( x => 10 , y => 20 , ); コードをMooseに匹敵するほど多機能なことがなんとなくわかるかと思います。 しかしattributeを多用しているため、Perlに詳しくない人にはとっつきにくそうです。 また、PSGIアプリやmod_perlでの利用に不安がありそうです。(ドキュメントを見た限りではmod_perl上での実行はサポートしているとのことでした。) CPAN上にあるモジュールでどれほど利用されているか調べたかったのですが、 不具合で調べられないようでした 。 Class::InsideOut Class::InsideOutの特徴は、 メモリ解放を自動でやってくれる コンストラクタ、アクセサの自動生成機能を提供 継承ツリーを汚さない Storableでのシリアライズをサポート attributeを使うクラスビルダー(Object::InsideOutなど)とは違って、PSGIやmod_perlアプリでもきちんと動作する Class::InsideOutでクラスを作る場合は、以下のようになります。 package Point { use Class::InsideOut qw( new public readonly private ) ; # read / write 可能なアクセサの生成し, このハッシュに登録されたインスタンスのインスタンス変数の開放処理は自動でされる public x => my %x_fields ; # read only なアクセサの生成し, このハッシュに登録されたインスタンスのインスタンス変数の開放処理は自動でされる readonly y => my %y_fields ; # アクセサは生成せずに、インスタンスのインスタンス変数を保持するだけのハッシュを作ることもできる # private memo => my %memo_fields; } my $p = Point->new( x => 10 , y => 20 , ) Mooseなどのように、普通の関数でアクセサの生成を行ったりしているので、コードはだいぶ読みやすいなと思いました。 CPAN上にあるモジュールでClass::InsideOutに依存しているモジュールの数は 23個 のようです。 (Moose|Moo)X::InsideOut  Moose, あるいはMooのバックエンドでInsideOutクラスを使うようにするモジュールもあります。 使い方は簡単で、(Moose|Moo)をuseしたあとに(Moose|Moo)X::InsideOutをuseするだけです。 次のコードはMooでの例です。 package Point { use Moo; use MooX::InsideOut; has x => ( is => 'ro' , required => 1 , ); has y => ( is => 'ro' , required => 1 , ); __PACKAGE__ ->meta->make_immutable ; } my $p = Point->new( x => 10 , y => 20 , ) 簡単にInsideOutクラスによるカプセル化が有効になり、かつMoose系のモジュールの機能が利用できてとても良さそうです。 しかし、インスタンス変数に相当する値が our で宣言されたハッシュに入っているため、外部からインスタンス変数を操作することができてしまいます。 Mooだと、具体的には以下のようにしてカプセル化を破ることができます。 my $p = Point->new( x => 1 , y => 4 ); say $p->x ; # -> 1 $ MooX::InsideOut::Role::GenerateAccessor:: FIELDS{$p}->{ x } = 100 ; say $p->x ; # -> 100 なぜ our にしてあるのかはわからないですが、個人的にはせっかくのカプセル化の利点がなくなってしまっているので使う理由はあまりないのでは、という気がしています。 とはいえハッシュベースクラスのインスタンスほどカジュアルにカプセル化に違反することはできませんし、そもそものちゃんとしたInsideOutクラスでも強引にカプセル化を破ることはできなくもないので、これを利用するかどうかは状況によって判断が変わってきそうです。 なお、CPAN上にあるモジュールでMoox::InsideOutに依存しているモジュールの数は 3個 で、MooseX::InsideOutに依存しているモジュールの数は不明です。 その他 Hash::FieldHash id:gfx 氏によって高速化&シンプルにされた Hash::Util::FieldHash のようなモジュールです Class::Std Damian Conway氏作のPerl Best Practiceで利用を推奨していたクラスビルダーです attributeを利用してアクセサやインスタンス変数格納用のハッシュを作ります 昔流行していたためか、けっこうClass::Stdに依存しているモジュールは多くて、 55個 ありました Dios これもDamian Conway氏作のInsideOutクラスのクラスビルダーです これを利用して書かれたクラスの外見はまるでPerl6のクラスのようになります 実験的なモジュールのようで、残念なことに最近のバージョンのPerlでは動かないようです。。 おわりに  InsideOutクラスのクラスビルダーはたくさん種類があるのですが、やはり広く利用はされていないんだなあ、と感じました。 個人的には、どうしてもInsideOutクラスを作りたい場合、読みやすくてかつ機能がそろっているClass::InsideOutを使うのが良さそうと思いました。 コアモジュールにInsideOutクラスの作成を支援するモジュールがあったのは意外でした。 7日目の担当は yunagi さんです。お楽しみに。
アバター
こんにちは、 2018年モバイルファクトリーアドベントカレンダー 12/7担当の id:yunagi_n です。 はじめに 個人的な趣味で、 Firebase と Nuxt.js でブログを作っています。 そのことについて、いろいろ話します。 なお、 会社で開発しているアプリ・プロジェクトとは一切関係ありません 。 前提 以下の環境で開発、動作確認を行っています。 Firebase (Blaze プラン) Node.js 8.12.0 Windows Subsystem for Linux (WSL) Ubuntu 16.04 Nuxt.js 2.3.4 TypeScript 3.2.1 Firebase と Nuxt.js のプロジェクトは作成済み 本題のその前に なぜ nuxt generate で生成した静的ファイルを使わないのか、についてです。 あくまで個人的な思いなのですが、 記事を Git 管理したくない 記事を publish するために、いちいち (たとえ CI であろうとも ) ビルド & デプロイを行いたくない という理由がありました。 とくにビルド時間というのはそこそこかかるので、やらなくても良いのならば、やらないに越したことはないです。 作るブログ ブログとして最低限の機能のみの実装です。 Firestore の制約上、記事の検索は出来ないので要件に含めていません。 記事を投稿できる URL は ID ではなく、 yyyy/MM/slug 形式で自由に設定できる 例: 2018/12/my-favorite-music 記事にはカテゴリを複数個付けられる カテゴリで絞り込みが出来る 投稿月で絞り込みが出来る 投稿数が分かる カテゴリ名 (投稿記事数) 、 yyyy/MM (投稿記事数) の形式で表示する 実装 Firestore の設計 Cloud Firestore は NoSQL データベースのため、 RDB のような SELECT COUNT を用いたカウントは行えません。 また、読み取ったドキュメント数によって課金が行われるだけではなく、読み取る数が多くなるとレスポンスも悪化するので、 一度の操作で読み取るドキュメント数は、できる限り抑えるべきです。 そのため、投稿数などは次のようなドキュメントに記録しました。 // カテゴリー export interface Category { name: string ; count: number ; } ドキュメントへの記事数の記録は、後述する Cloud Functions の Cloud Firestore トリガー を使って行います。 ブログ記事は単純な、以下のドキュメントにしました。 export interface Entry { slug: string ; title: string ; body: string ; created_at: Date ; // 実際は秒数が降ってきます categories: string [] ; } Cloud Functions Cloud Functions では、記事の投稿・更新・削除をトリガーに集計を行う処理と、ページの描画処理を登録します。 例えば記事を投稿したときの処理は import * as firebase from "firebase-admin" ; import * as functions from "firebase-functions" ; module . exports = functions.runWith ( { memory: "256MB" , timeoutSeconds: 30 } ) .firestore. document ( "entries/{entryId}" ) .onCreate (async ( snapshot ) => { const entry = snapshot.data () as Entry ; for ( let name of entry.categories ) { const category = await firebase.firestore () .collection ( "categories" ) .where ( "name" , "==" , name ) .limit ( 1 ) . get() .docs.shift (); if ( category ) { // すでにあればカウントアップ await category.ref.update ( { count: ( category.data () as Category ) .count + 1 } ); } else { // 無ければ作る await admin.firestore () .collection ( "categories" ) .doc () . set( { name , count: 1 } ); } } } ); となります。 更新・削除も同様の処理を記述していけば OK です。 Nuxt.js と Cloud Functions での SSR は以下の通り。 buildDir プロパティは、うっかりソースコードからの相対パスで書いてしまうと、何も返ってこなくなります。 import * as express from "express" ; import * as functions from 'firebase-functions' ; import { Nuxt } from "nuxt" ; const app = express (); const nuxt = new Nuxt ( { dev: false , buildDir: "./lib/.nuxt" , // package.json がある場所からのパス build: { publicPath: "/assets/" } } ); async function handleRequest ( req , res ) { return await nuxt.render ( req , res ); } app.use ( handleRequest ); module . exports = functions.runWith ( { memory: "256MB" , timeoutSeconds: 20 } ) .https.onRequest ( app ); Firebase Hosting firebase.json を編集して、 Firebase Hosting のリライトルールを変更します。 基本的には全てのリクエストを Cloud Functions の関数を呼び出すように設定します。 { " hosting ": { " public ": " dist/client ", " ignore ": [ " firebase.json ", " **/.* ", " **/node_modules/** " ] , " rewrites ": [ { " source ": " ** ", " function ": " render " } ] } } Nuxt.js npx create-nuxt-app で作ったものをそのまま使います。 プリセットにある Tailwind CSS を使うと、CSS が苦手でも悩まされることなく UI を作れるのでオススメです。 Nuxt.js の Firebase の初期化は、 plugins/firebase.ts で行います。 これは Firebase の初期設定ページに表示されているものをそのままコピペしてきます。 import * as firebase from "firebase" ; if ( ! firebase.apps.length ) { const config = { apiKey: "xxxxxxxxxx" , authDomain: "xxxxxxxxxx.firebaseapp.com" , databaseURL: "https://xxxxxxxxxx.firebaseio.com" , projectId: "xxxxxxxxxx" , storageBucket: "xxxxxxxxxx.appspot.com" , messagingSenderId: "1234567890" } ; firebase.initializeApp ( config ); } export { firebase } ; そして、 nuxt.config.js の plugins プロパティに plugins/firebase.ts へのパスを追加しておきます。 plugins: [ "~/plugins/firebase.ts" ] Firestore からデータを持ってきて、 SSR もしくはページ遷移時に取得するには、 asyncData メソッドを使用します。 ファイル名・ディレクトリ名を /_year/_month/_slug.vue のようにすることで、 引数として context が渡されるので、 その中の params プロパティ経由で、 year , month , slug といったパラメータを受け取ることが出来ます。 // SFC の script 部分のみ import { Component , Vue } from "nuxt-property-decorator" ; import { firebase } from "~/plugins/firebase" ; @Component export default class extends Vue { public async asyncData ( { params } ) { // const { year, month, slug } = params; でパラメータ取得できます。 const entry = await firebase.firestore () ... ; // 記事取得 return { entry } ; } } asyncData メソッドの返り値は data メソッドの返り値とマージされるので、あとは通常の Vue SFC のように テンプレートや他のメソッドなどを書くだけで OK です。 ブログサイドメニューにあるような、カテゴリ一覧や月別アーカイブは、 store でデータを取得しました。 // 必要な部分のみ const actions = { // ページ遷移でサイドメニュー更新しなくても良いので nuxtServerInit メソッドを使っています async nuxtServerInit ( { dispatch } , context ) { await dispatch ( "fetchArchives" ); // ... } } ; export default () => new Vuex.Store ( { actions , // ... } ) あとは、この値を Getter や store プロパティ経由で取得・設定することで、サイドメニューの表示が実装できます。 まとめ 簡単にですが、 Firebase と Nuxt.js を使うことで、個人でもブログのような Web アプリケーションを作る事を紹介しました。 明日は @umaaaaa さんの記事です!
アバター
こんにちは、シニアブロックチェーンエンジニアを名乗っている @koropicot です。 これは 2018年モバイルファクトリーアドベントカレンダー 5日目の記事です。 前日は 元モバイルファクトリーの @karupanerura さんの 非TLS時代のセキュアなデバイス認証の思い出 でした。 はじめに モバイルファクトリーは2018年より本格的にブロックチェーンに参入し、ブロックチェーン上の分散型アプリケーション(DApps)を広めることを目指すUniqysプロジェクトというものを進めています。 このプロジェクトの中で、ブロックチェーンを勉強しブロックチェーンを作るに至りました。 プロジェクト自体の話は サイト などに譲るとして、この経験を元に、ブロックチェーンの技術領域のそれぞれについてどう学ぶかを共有します。 ブロックチェーンの全体像を学ぶ まず、個々の技術領域に触れる前に、ブロックチェーンの全体像をざっくり把握します。 なぜなら単にブロックチェーンといっても、他の多くのシステムと同じく、多くの構成要素が関連しあうことで全体で満たしたい要件を実現しているからです。 では、ブロックチェーンとは何でしょうか。 実は最初に把握したいこの全体像が、ブロックチェーンにおいて1番わかりにくい部分かもしれません。 なぜなら、立場やプロジェクトによってブロックチェーンをどう定義し何を重視するかが異なり、それぞれが何の話をしているのか混乱するためです。 そのため、例えばBitcoinについて学んでブロックチェーンを理解した気になっても、別のプロジェクトについて調べた途端に何も分かってなかった気分になります。(なりました) これを乗り越えるためには、どのようなレイヤーが存在しそれぞれがどこの話をしているかを意識するとわかりやすいです。 ざっくり書こうと思ったのですが、以下の記事によくまとまっていたので委譲します。 https://magazine.ginco.io/post/kisochishiki_blockchain_ecosystem/ この記事における、ビジネス・事業領域と技術開発領域が、エンジニアの関わる領域となります。 それぞれについて見ていきましょう。 アプリケーション(DApps)領域を学ぶ ビジネス・事業領域では、エンジニアはこれまでのアプリケーションの開発と同じように、サービスを実現するために使える技術を把握して選択し、それらを用いてアプリケーションを実装することが求められます。 そのため、この領域の学習は既存のアプリケーション開発における技術の習得と同じ方法が有用です。 人によってやりやすい方法があると思いますが、自分の場合はどうしていったかを記します。 選択肢を学ぶ 既存のサービス、例えばWebアプリケーション開発でも、OS・サーバ・DB・言語などの技術を適切に選択するためには、それら自体やその長所・短所を幅広く知っている必要があります。 これはブロックチェーンを用いたアプリケーションでも変わりません。 自分の場合は、事業として技術開発領域にも挑みたいということから、競合としての既存のDAppsプラットフォームについて調べましたが、事業領域でアプリケーションを開発する場合にも、選択肢を事前に把握しておくと良いでしょう。 索引として、DAppsを開発するうえで知っておきたい、選択肢になりうるブロックチェーン(レイヤー2を含む)についてざっくり示しておきます。 Ethereum 現在DAppsを実現するプラットフォームとしては最も人気でコミュニティも活発 パブリックチェーンは(現在のところ)PoWを用いており処理時間と実行にかかる手数料が課題 UX的に受け入れられるアプリケーションでは第一の選択肢 Hyperledger Fabric 主にコンソーシアムやプライベートチェーンを実現するための基盤 大手企業がプロジェクトに参入 既存のビジネスにブロックチェーンを適用する場合には有力な選択肢 Uniqys Kit (知っておきたいに入れるのは半分ポジショントーク) Ethereumのサイドチェーン(レイヤー2)としてスケーラビリティの改善と容易な開発を目指し開発中 サイドチェーンとPBFTベースのコンセンサスによりアプリケーション毎の素早い処理が可能 Ethereumをそのまま使うだけではUX的に問題のある場合に選択肢となる Loom Network Uniqys Kitと結果的に同じような感じ My Crypto HeroesというDAppsが利用 素早い処理が可能でEthereumをそのまま使うだけではUX的に問題のある場合に選択肢となる EOS パブリックなDAppsのプラットフォームとしては中国などで人気 DPoSによりEthereumよりも高速な処理を実現し開発者がリソースの費用を負担することで利便性を向上 Ethereumに比べると発展途上なのと情報が日本だとまだまだ少ないのが課題 他にも関連するブロックチェーンやソフトウェアも多くありますので、上記の選択肢と比較すると理解しやすく、ユースケースにおいて適切な選択肢が見えてきます。 とはいえ、Webアプリケーションなどに比べればまだまだ発展途上なことや、ブロックチェーン特有の課題から、最適な選択肢が見つからないかもしれません。 そのような場合は、一旦下のレイヤー(プロトコルレイヤー)に降りて見ると、ブロックチェーンとしてどのようなトレードオフがあるかが見えてきて、選択の手助けになります。 開発を学ぶ DAppsの実装は、それぞれのブロックチェーンによってプログラミング言語やアーキテクチャ、開発ツールが異なるのでそれらに合わせて学ぶことになります。 Ethereumを用いる場合は、 CryptoZombies という学習サービスが有名です。 アプリケーションによっては検証可能にするためコードを公開しているものも多く、これらも参考になります。 例えば、CryptoKittiesというEthereum上のDAppsのコードは ここ で確認できます。 また、ブロックチェーンとやり取りするクライアントとしてはWeb3というものがデファクトスタンダードで、バージョン1.0が絶賛開発中で、インターフェイスが変更されているので、今から始めるならこちらがおすすめです。(ただしバグも多いです。コントリビュートチャンス!) またこれは宣伝ですが、Uniqys Kitを使う場合はDAppsを既存のWebアプリケーションのように作成できます。 そのため最初の学習のハードルは低くできていると自負しています。 いずれにしても、この領域は結局のところ既存のアプリケーションの学習とほぼ同じです。 開発だけなら特に仮想通貨もいらないので、実際に作ってみるのが良いと思います。 ただし、特有の注意点として、サービス自体や全体の設計に関わる以下の特徴は最初から意識しておくと良いです。 パブリックチェーンの場合は処理は誰でも確認できてしまうこと チェーンによって異なるが処理に数秒から数十分かかること チェーンとの接点となるノードは信頼しないといけないこと(場合によっては管理下にノードを構築する) アカウントの認証は多くの場合秘密鍵に依存していてその管理は自己責任であること ブロックチェーン外の情報は基本的に扱えないこと 誰にも操作されない乱数も容易には実現できないこと サービスを展開する地域の法律(日本の場合は資金決済法など)によっては規制があること プロトコルレイヤーを学ぶ 技術開発領域はプロトコルレイヤーとも呼ばれ、コンセンサスアルゴリズムなどの分散システムや暗号理論、インセンティブ設計、オフチェーンやサイドチェーン、コントラクト検証などの数多くの部分からなります。 これらの領域は課題が多く存在していて、チャレンジのしがいがありますが、一方で事業には直結し辛い部分のため人口も少ない印象があります。 しかし、課題を解決しないことには事業を含めたブロックチェーンの発展に限界があるため、ぜひとも多くの人に興味を持ってほしい領域です。 この領域を学ぶ場合は、理論を学ぶことと、それらをどう実装していくかの両面が必要です。 理論を学ぶ 理論を学ぶ際には、まずは何はともあれBitcoinの原論文を読むと良いです。 PDFを こちら から読むことができます。 また、日本語での解説も多くあります。 ここで注意しなければいけないのは、前述したようにBitcoinがブロックチェーンの全てではないということです。 しかし、Bitcoin以後に登場したブロックチェーンはBitcoinの課題を解決したり適用範囲を広げるために変化しているので、基準として知っておくのは有用です。 この次に何を学ぶかですが、概要さえわかっていればより深い部分は興味や必要がある場合に応じて進めていけばいいと感じています。 例えばUniqys Kitを作る上では、スケーラビリティの課題を解決する必要があったことから、レイヤー2の部分を調べ、必要に駆られてコンセンサスアルゴリズムについて学ぶという過程を経ました。 それ以外の部分は、何のためのどういうものかと言うのを知っている程度です。 では、それらをどのように学ぶのが良いでしょうか。 分散システムや暗号理論、形式的検証などはブロックチェーンの登場のはるか以前から存在している分野です。 またインセンティブ設計などもゲーム理論として捉えることができるでしょう。 そのため、これらの分野の既存の論文を当たるのが良いと考えています。 プロジェクトのホワイトペーパーなどを読むのも良いですが、やはり理論はちゃんとした土台があってこそ活きてきます。 また、今後のブロックチェーンの発展のためにはいかに既存の専門家をブロックチェーン界隈へ連れてくるかが重要になってくると思います。 実装を学ぶ 理論が理解できても、それを現実にしなければ利用することはできません。 また、理論も実際に手を動かしてみるとより理解できることもあります。 これらの実装の理解には、近しいプロダクトのソースコードが1番勉強になります。 ペーパーやドキュメント、参考書を読んでも理解できない部分や、腑に落ちないことはどうしてもあります。 そういったときに、もっとも具体的で、現状を反映しているのがソースコードです。 自分がUniqys Kitを実装した際にも、EthereumやTendermint, NEOといったブロックチェーンのソースコードを読んで参考にしました。 とはいえソースコードを読むためには、大規模すぎて取っ掛かりが分からなかったり、言語やプロダクトの文化や歴史が分からなかったりするなど難しい部分もあります。 そこで、これも宣伝なのですが、Uniqys Kitの実装は比較的分かりやすい(らしいという id:odan3240 さんの意見)ので、ぜひとも参考にしてほしいと思います。 まとめ ブロックチェーンは複数の領域からなるのでどの領域についての話かを意識して学ぼう ブロックチェーン上のアプリケーションはいままでと同じように手を動かせばわかる 理論に興味あれば既存の研究をちゃんとちゃんと追って巨人の肩の上に立とう やっていきましょう。 明日は6日目 id:mp0liiu さんです!
アバター
2018年モバイルファクトリーアドベントカレンダー 12/3担当の id:yashims85 です。 前日は id:odan3240 さんの NuxtMeetUp#5 でnuxt-i18nを用いたwebサイトの多言語化について話してきました でした。 まえがき 2018年今日において、クロスプラットフォーム(以降: X-PF)界隈を改めて見てみると、大手各社がそれぞれアーキテクチャを提供をしており、X-PF戦国時代の様を呈しています。 今回はその中でもKotlin/Native(以降: K/N)とFlutterに絞って比較を行いたいと思います。 確認した環境はそれぞれ2018/12/03時点で最新である以下です。 Kotlin - 1.3.10 (Kotlin/Native Beta) Flutter - v0.11.13-beta 実のところ、両X-PFを触る前は何を書こうか迷っていたのですが、K/N、Flutter、及び他のX-PFを触ってみて感じるのは、どのX-PFにも長短があり現時点での銀の弾丸は無いという事です。 そこで、今回はKotlin/NativeとFlutterの比較を行いそれぞれの長短を把握してもらうことで、自分の状況にあったX-PF選びの参考にしてもらえたらと思います。 今回やらないこと Getting Started 各PFのGetting Startedが引っかかる部分も無かったので、そのとおりにやったら良いと思います 最強のX-PFはコレだ! X-PFについて Android/iOSの両モバイルOSの登場以来、古今東西様々なX-PFが生まれては消えていきましたが、現在X-PF界隈に置いては、大手デベロッパーを背後に持つX-PFに淘汰されて来ています。 一例をあげてみましょう。 X-PF 主開発社 言語 特徴 Xamarin Microsoft C# X-PFとしての歴史、VisualStudioを使用した快適な開発環境、各PFそれぞれのUI実装 Unity Unity C# リッチコンテンツに強み、共通UI、独自ライフサイクル、ゲームエンジンベース ReactNative Facebook JS Reactと同じノリで作れる、共通UI Kotlin/Native JetBrains Kotlin AndroidStudioをIDEとして使用できる、各PFそれぞれのUI実装、まだBeta Flutter Google Dart Google系サービスならポン付け実装できる、独自ライフサイクル、基本は共通UI、まだBeta この中でもKotlin/NativeとFlutterはAndroidエンジニアにとって馴染みの深い2社が開発していることもあり、 気になっている方も多いんではないでしょうか? Kotlin/Native 現在のAndroidの標準開発言語であるKotlinを開発したJetBrains社が主導で開発を進めています。Kotlin 1.3からBeta機能としてKotlin/Nativeが提供されています。 K/NはKotlinという言語に対してX-PF機能を付加するアプローチをとっています。 K/Nは以下のような特徴があります。 宣言を実装を分けることができる言語仕様と各PF用のビルドシステムによって実現 AndroidではJVM言語として、DalvikVM、ART上で実行される iOSではLLVMで機械語にコンパイルされ、iosのframeworkとして吐かれる frameworkにはK/Nコードのシンボルがヘッダファイルとして包含されているため、Swift側からもK/Nのコードを参照できる UIの共通化機能は提供されておらず、それぞれのPFで実装する必要がある Flutter Googleが主導で開発を進めています。こちらもBetaですが、Betaとは思えないほど機能やドキュメントが充実しています。 Flutterは純粋に開発環境の共通化を目的として作るアプローチをとっています。 Flutterは以下のような特徴があります。 Dartは滅びぬ!何度でも蘇るさ! Android/iOSともに機械語にコンパイルし実行する UIパーツが多数提供されており、独自性の強いデザインを押し出さなければ爆速開発が見込める Reactに影響を受けたアーキテクチャ設計 各OSの機能とは遠い 開発環境 Kotlin/Native Flutter Android Studio / IntelliJ IDEA Android Studio / IntelliJ IDEA, VSCode 当然といえば当然ですが、どちらもAndroid Studioがそのまま使えるのが面白いですね。 どちらもBetaなので細かくは言及しませんが、現段階ではK/Nはディレクトリを作ったりbuild.gradleをいじったりと結構手作業で環境を構築する必要があり、若干の敷居の高さを感じます。 対してFlutterは、ASにPluginを導入するだけでFlutter環境をセットアップできるので、拍子抜けするほど楽ちんです。 またX-PF開発ではXcodeを触る時間をいかに少なくするかが鍵だと私は考えています。 この点においてもFlutterは現段階で一歩リードしており、Signing周りをポチポチするだけです。 対してK/NはBuildPiplineに手を加える必要があり、AppleのBuild周りの知識を要求されます。 言語 Kotlin/Native Flutter Kotlin Dart Kotlin/Native 言語的好みで言えば圧倒的にKotlinなのですが、X-PFを前提としたときK/Nは複雑性が上がり、可読性やメンテナンサビリティの低下につながる可能性があると感じました。 例えば、K/NではX-PF実現のために、1つのクラスを基本的には common に実装しますが、 必要に応じて宣言だけをcommonで行い、実装は Android iOS ... と複数のファイルに分割できるようになっています。 逆に、各PFの実装からcommonの処理を呼び出すこともできますので、きちんと設計を行わないと簡単にスパゲッティーコードを生み出すことができてしまいます。 また、非同期処理周りでも制約があり、現状ではCoroutineでの実装をおすすめしているようです。Rxなどは各言語の実装によるため異なる挙動を示す可能性があるためでしょう。 このへんの設計の難しさをクリアすれば、クライアントのみならずサーバともビジネスロジックを共有できるため、夢が無限に広がっていきます。 Flutter Flutterでは各PFの実装が処理の途中に挟まる事がないので、挙動の差異を気にする必要は最小で済むかなという感じです。 ドキュメント Kotlin/Native ドキュメントはありますが、ごちゃっとしていて全体像を理解するのに少し苦労します。とはいえ、不足していると感じるシーンは無く必要十分ではあります。 Flutter ドキュメントの整備度合いはFlutterに一日の長がありそうです。 Dart初心者に対してもEffectiveDartも準備されており、言語的マイノリティーに対してある程度の安心感があります。 周辺ライブラリ どんなライブラリが揃ってるかざっと見るのはawesome探すのが一番ですね。 Kotlin/Native Flutter awesome-kotlin-native awesome-flutter Kotlin/Nativeのawesomeが全然awesomeじゃないよ。。。 依存管理 Kotlin/Native Flutter Gradle Gradle どちらもASで開発できるので、必然的にGradleになるでしょう。わかる。 システム設計 まず、Kotlin/NativeやFlutterの特性を見ていく前に、プロダクト全体に対して与える影響をそれぞれ考えてみましょう。 Kotlin/Native K/Nは逆に設計に注力する必要がある、ある程度の規模が見込まれるプロダクトに向いています。 Clientだけでなく、サーバサイドまで1コードで実装できるのはとても魅力です。 K/Nは各PFで積み上げた資産やライブラリを有効活用しつつ、ビジネスロジックを共通化するアプローチをとれるとれるので、 システム設計の観点ではK/Nだから生まれる制約というのは特に無く、それぞれのPFの制約に依存する感じになります。 Flutter Flutterはミニマムで最速にリリースするのに向いています。 ただし、その分設計の自由度は少なく、他のアプリと差異を出すために特殊なことをしだすとFlutterのメリットは途端に消滅していく印象です。 サーバサイドについてもGoogleのサービスであれば一通りライブラリは揃っているけど、離れるほど少なくなる印象です。 サーバレスアプリケーションも(Googleのサービスベースなら)作りやすいんじゃないでしょうか。 ソフトウェア設計 それぞれのX-PFを採用したとき、それぞれどのようなソフトウェア設計が行えるでしょうか?X-PFは抽象化度によって、取りうるソフトウェア設計の幅に差が出てきます。 Kotlin/Native K/NではBetaになって日も浅いこともあり、めぼしい設計アーキテクチャは見当たらない印象です。 Pure-Kotlin向けならありますが十中八九JVMに依存しており、そのままK/Nで使用できるとは限らないため判別が付きません。 また、K/Nは言語の項目で言ったとおり設計の難しさがあり、特に多人数で開発するプロダクトでは注意が必要です。 基礎設計を行う時間を取れるプロダクトでは選択する価値がありますが、大規模でとにかくリーンというプロダクトではそれなりの負債を覚悟しなければなりません。 逆にそこがクリアできるのであれば、設計の自由度は高く、好きなように実装できると思います。 Flutter Flutterに関してはReactにインスパイアされたfluxアーキテクチャで実装されており、モダンなアーキテクチャを強制されます。 また、先行事例も多く参考を得やすいでしょう。 UI設計 UI設計に置いてはどういった長短があるでしょうか?近年の開発現場では非エンジニア職が直接UIの実装を行うシーンもしばしば見られます。 そうしたとき、UIの設計/実装のしやすさは判断基準の一つになります。 Kotlin/Native K/NではUIは各PFで実装する事になります。それぞれのPFに適したUI設計ができる反面手間ではあります。 現在Android/iOSともにGUIでデザインできる機能が備わっているので、それがそのまま利用可能です。 Flutter Flutterではレンダリングも独自で行っており、HotReloadに対応してるのが強みです。 UIパーツはWidgetという単位で公式/非公式問わず公開されており、ありものを拾ってくるのが容易です。 UIもコードで表現されるので、UIの修正が手間な印象を受けます。 ただし、Flutter自体がfluxアーキテクチャで動いており、そのへんの流れを知ってないと大規模なUI設計は難しい印象を受けます。 まとめ いかがでしょうか?個人的な印象としては K/Nは選択肢としては選択可能であるが、現段階では自由度が高い半面、開発難易度も高い。 Flutterは開発効率は高いが融通の効かない部分も多く、将来的に手詰まりになる可能性はある という印象を受けました。 まとめとしては、まえがきに書いた事の繰り返しになってしまいますが、X-PFにおいて銀の弾丸は無いので、 自身の開発内容にあった特性のX-PFを選択していきましょう。 明日は id:karupanerura さんの 「非TLS時代のセキュアなデバイス認証の思い出」 です。
アバター