コネヒト開発者ブログ

コネヒト開発者ブログ

CakePHP3.4.5にアップデートしました!

f:id:itosho525:20170430222524j:plain

こんにちは! 3月からコネヒトで頑張っているアイドル大好きエンジニアの@itoshoと申します。 最近はBiSHさんの『プロミスザスター』をヘビロテしています。

いきなりですが、皆さんは普段Webアプリケーションフレームワークのバージョンアップをどういうタイミングで実施していますか? 影響が少なそうなマイナーバージョンアップであっても、ついつい先延ばしにしてはいないでしょうか。

しかし、そうやって先延ばしにしていると、いざバージョンアップしようと思った時に影響範囲のチェックや動作確認のコストが大きくなりがちで「まだ暫くこのままでいいか…」と二の足を踏んで、気付けばいつの間にか最新バージョンに追従出来ないレガシーなシステムを生み出してしまうことになりかねません。(僕も過去にそのような苦い経験がございます…)

コネヒトではWeb APIの開発をCakePHP3を利用して行っているのですが、そのようなシステムを生み出さないように、新しいバージョンが出た際は積極的にバージョンアップするように努めていまして、今日はつい先日実施した3.4.4から3.4.5へのバージョンアップの内容について書きたいと思います。

ちなみに、CakePHPのHTTPリクエスト / レスポンスのインターフェイスが3.4系からPSR-7準拠に生まれ変わっており(素晴らしい!)、今後deprecatedなメソッドが増えることが予想されるので、まだ3.3以下のバージョンを利用されている方はお早めのバージョンアップをオススメします!

CakePHP3.4.5の変更点まとめ

詳細な情報は公式のリリース情報をご覧いただければと思いますが、今回は以下の変更がありました。

  • PaginationHelperで複数の値が設定されたソートリンクがデフォルト以外のモデルだと正しく生成されない不具合の修正
  • CSRFエラーとMissing Controllerが発生した際のエラーメッセージの改善
  • Diactorosというライブラリ起因で発生するHttp\Client\Requestクラス内でのエラー修正

また、公式ページに記載されているのは上記の3つなのですが、changelogをみてみると.travis.ymlからHHVMの記述が消えているので、興味ある方は実際のコードを読んでみると思わぬ発見があるかもしれません。

アップデート時にやったこと

3.4.4からのアップデートで、今回は軽微な不具合の修正のみだったので、composer updateコマンドを実行後、CIが通っていればOKとして、リリースを行いました。(アップデート後も特に不具合は起こっていません)

こういう時、きちんとテストコードを書いておくと安心ですね!

おわりに

今回はマイクロバージョンアップなので、問題なく最新バージョンにアップデートすることが出来ましたが、これからCakePHPをバージョンアップした際はこのブログでご報告させていただいて、微力ながらCakePHP界隈を盛り上げていくことをプロミスしますので、今後ともよろしくお願いいたします!

追伸

この記事をしたためている間に3.4.6がリリースされたので、それについても近日中にまとめたいと思います!

チームでのAPI開発の強い味方!!REST APIクライアント「Paw」と「Insomnia」を比較してみた

f:id:supermanner:20170502152129j:plain

こんにちは!今年もコナン映画にいってきました、コナンでは服部派のエンジニア結城(@super_manner)です(*´ڡ`●) さて、今回はAPIをチームで開発するうえでつよーい味方になるツールを2つ使い比べた結果をご紹介しようと思います!!

そもそもPawとInsomniaとは?

双方ともREST APIクライアントです。

Paw paw.cloud Insomnia insomnia.rest

APIを作成していると、POSTする必要があったり、User-AgentやRequestHeaderによる制約を受けたりで プラグイン追加が加速したりしますよね。
うっかりそのまま他のサイトを閲覧して全部がxmlで表示されたりすることもしばしば。
そんな煩わしさも、これらのクライアントを使うことで開放されるのです!!
APIをメインに開発されている方にはもはや必需品になっているかもしれませんね。 百聞は一見にしかずなので、試しに国土交通省のAPIを叩いた画面を見てみましょう。 *1

PawでGETした様子 f:id:supermanner:20170427205919p:plain InsomniaでGETした様子 f:id:supermanner:20170427210037p:plain

このように、各種設定をしてあげることで簡単に何が返ってくるかを確認することができます。

共有ツールとしてのおすすめポイント

わかりやすいUIなので、初めて使う方でも迷うことなく使っていただけるのがいいところです。
以下、参考画像はInsomniaのものですが、Pawも同じ機能がついています。(詳しい比較は後述します)
このように、階層構造でendpointを管理できたり、パラメータやヘッダ情報もまるっと共有できます! f:id:supermanner:20170501183349p:plain f:id:supermanner:20170501192159p:plain こちらは、環境変数を管理できる機能です。共通して使う変数と、そうでないものを切り分けできて便利です。 f:id:supermanner:20170501205120g:plain

また、snippetを自動生成してくれるのでPull Request等に貼り付けると、レビュワーに優しいですね\(^^)/ f:id:supermanner:20170501195328g:plain

2つのツールの機能比較

さて、ツールの紹介が終わったところで本題です。
コネヒトのAPI開発陣はトライアル期間を経て2つのツールの選択をせまられました… ツールの選択はいついかなるときも難しいものですよね。
ということで、2つのツールを比較し、できること、できないことを表にしてみましたので御覧ください。

Paw Insomnia 備考
RequestHeaderの設定
パラメータの設定
cookieの設定
環境ごとの変数の設定 例えばですが, {local/dev/stg/prod} 等で別途環境変数をマネージする機能があります
snippet発行 ただし双方で発行できるsnippetの種類に違いがあります
設定のimport/export
複数のapiを同時に叩く
teamアカウントを発行できる teamを作成することで、登録したapiの設定を共有できます
ブランチ機能がある ブランチ機能については後述します
料金 個人プラン:$49.99
チームプラン:月額$10
制限付き$0
有料プラン:月額$5
チームプラン月額:$8

Pawのほうが少し料金が高めですが、その分機能が多いですね。
InsomniaでできることはPawですべて網羅していると言っても過言ではないでしょう。(もちろんsnippetの発行形式など細かな違いはあります)
また、個人でcloudにプロジェクトをもつことのできる個人プランはいいお値段です!
そんな多機能なPawですが、中でも特筆すべき機能としてブランチ機能があります。

Pawのブランチ機能について

公式のドキュメントが非常に丁寧に書いてあるので、基本的な使い方はこちらを参照していただけると良いかと思います。 さぁ、セクションタイトルを見てみましょう!

Manage your workflow with branches

何処かで見たような機能ですね。そうです!我々も馴染みが深いgitと同じです。 開発中のapiなどはいきなりみんなに共有しても「これ開発中です!」と宣言して回る訳にはいきません。
そのような場合は自分用にブランチを切って、そこで登録していけばmasterはスッキリと余分なものが登録されないまま保たれます! チームにとっても、自分にとっても常に動くものが簡単に手に入りますb

Insomniaのデータ共有はどうなっているの?

先程の表で述べた通りInsomniaにもteam機能があり、workspaceとよばれる箱でデータを共有することができます。
が、現時点では手元のデータとremoteのworkspaceの同期だけで、git pullのような機能は実装されていません
つまり、手元で変更してしまうとその時点で残念ながらremoteのデータとはズレが生じてしまい、復旧の術がないということです。
dropboxをイメージしていただけるとわかりやすいかと思います。 cloud上のworkspaceと完全一致していない場合は下図のようにどれだけズレているのかが%で表示されています。
f:id:supermanner:20170429174502p:plain

英語ドキュメントの読み逃しがこわかったため、運営チームに問い合わせをさせていただいたところ、下記の返答を頂いたので将来的には実装されるかもしれませんね!!期待して待ちましょう。

Hi Mana,

Thanks for reaching out :)

There is no official way to do a pull master currently. For now, I recommend duplicating the request (or folder) that you want to edit so that you do not modify the existing data.

I will be sure to consider this use case as I improve the team features in the future.

Let me know if that works for you.

まとめ

2つのツールの機能比較をして、現在私のチームではInsomniaにチーム課金して使用しています。
なぜなら、いまのところREST Clientの使用理由が

  • 実装したAPIの各環境での実行確認をするため
  • テストで使用したrequest情報を完全再現してレビュー時にレビュワーにわかりやすくするため

の2つであるからです。
ですので、機能としてはシンプルで、料金もお財布に優しいInsomniaを採用し日々の開発に役立てています。 しかし今回記事を書くにあたって改めて調べてみるとPawの機能はまだ全然つかいこなせていなかったなぁと思ったので、 今後も使って有用だった機能があれば引き続き紹介していきたいと思います!*2

双方ともまだまだ開発は進んでいるようなので、みなさんもよければぜひ活用して素敵なAPIライフを送ってください!

*1:http://www.land.mlit.go.jp/webland/api.html

*2:pawのdocの厚さがものすごい

デザイナーの生産性を高める3つの改善策

f:id:connehito:20170331142331p:plain

お疲れ様です!デザイナーのきよえし(@kiyoe_furuichi)です。 今回は「生産性」というテーマと戦って得た学びについて書いてみたいと思います。 少し長いので、お茶でも飲みながらゆっくり読んでくださいね。

生産性とは

インプット(成果を生み出すためのヒト・時間・情報など)に対するアウトプットの比率のことを「生産性」と言います。 生産性は、少ないインプットで大きなアウトプットを得られると生産性が高いと評価され、大きなインプットの割にアウトプットが小さいと生産性は低いとされます。

デザイナーのお仕事で例えると、バナーを1日で作成してリリースするのと、半日で作成してリリースをするのとを比較して、成果物は同じものだとすると後者の方が「生産性が高い」ということになります。

きっかけ

「忙しい」を理由に思考時間を減らすのをやめたいと思ったことがきっかけでした。 サービスの成長と共にチャレンジできることが増え、好きな領域で活躍できることが嬉しい一方、目の前のissueに追われ本質からモノゴトを考える余裕がなくなっていました。 そんな状況だと質の高いアウトプットは出せない・精神衛生上良くない = 生産性が低下するという問題意識から、自分の仕事のやり方を見直すことにしました。

実践

生産性を高めるために実践したことは以下の3つです。

  1. RescueTimeで消費時間を可視化する
  2. issueを細かく分解する
  3. 情報を断捨離する

補足すると、今回は特に問題意識の高い「時間の使い方」にフォーカスして実践しました。時間の使い方が上手になると本質的な作業への消費できる時間が増えるため、デザインの質が上がり、生産性が高まると考えたためです。

1. RescueTimeで消費時間を可視化する

RescueTime(以下レスキュータイム)というツールを使って、1日の作業内容のうち、本質的な作業にどれだけ時間を消費できたかどうかを計測します。

例えばこのログをご覧ください。私の記念すべき初レスキュータイムの結果です。

f:id:connehito:20170330201240p:plain (本質的な作業 = Software Development + Design & Composition)

グラフを見ると、1日の作業時間の約30%がCommunication (to Slack)に消費されていることがわかります。本質的な作業よりもコミュニケーションの時間が掛かっているのは健全ではありません。 レスキュータイムのログを毎日退社前に見ながら、1日を振り返って改善ポイントを洗い出します。 ちなみにこの日は以下のような反省をしました。

Slackの閲覧時間が多くなってしまう原因として、モニター上に表示しっぱなしで新着の投稿があると気になって開いてしまうことがあげられる。必要なとき以外は目に止まらないようにウィンドウを非表示にしたり不要なチャンネルをミュートするなどで工夫をしよう。

2. issueを細かく分解する

(このやり方はgithub/ZenHubを利用した進捗管理を行なっている方に限定されます)

issueの粒度を細かくすることでアウトプットのゴールを明確にします。 そうすることで1作業に対しどのくらい時間を消費したかどうかが見やすく振り返りもしやすくなります。

例えば「新しい機能のUI設計」というissueがあるとします。細分化するとそのissueをepic(親)としてUI設計に必要な工程ごとにこのように分けることができます。

  • 【epic】 新しい機能のUI設計
    1. 【issue】 アイデア出し
      • ゴール:頭の中でイメージを固める
    2. 【issue】 ワイヤー作成
      • ゴール:ディレクターにOKをもらう
    3. 【issue】 Sketchデータ作成__iOS
      • ゴール:prott確認ができる状態に落とす
    4. 【issue】 Sketchデータ作成__android
      • ゴール:prott確認ができる状態に落とす
    5. 【issue】 確認・修正
      • ゴール:ディレクター・エンジニアと確認を行い、OKをもらう
    6. 【issue】 Zeplinデータ納品
      • ゴール:ZeplinデータをiOS/androidエンジニア両方に納品する

細分化すると一覧として見たときにすごい量になり管理が難しくなりますが、ZenHub(ゼンハブ)というプロジェクト管理ツールでissue管理を行い、次やるべきissueや進捗が明確になるようにしています。

f:id:connehito:20170330201327p:plain

in Progress が現在着手しているissueで、in Reviewがレビュー中、そしてDesign には1スプリント内に着手するissueが順番に並んでいます。

3. 情報を断捨離する

1に記載したレスキュータイムを始めてから、コミュニケーションで時間を消費しすぎているという課題を発見したため、見るもの・見ないものを分別して情報の断捨離を行いました。 主に以下の3つを意識して時間の節約を行なっています。

① タイムラインは閲覧を控える

Slackやカレンダー、SNSなどタイムラインを追うツールはマインドシェアを取られがちなので必要以上には見ないようにしています。
特にSlackの分報の使い方には注意しています。気軽に気づきやつらみを共有することを目的とするチャンネルですが、ついおしゃべりをしてしまうので時間を区切って見るなど常駐しないように気をつけています。

② 大事なことを見逃さないように工夫する

タイムラインの閲覧を控えると、タイムラインが流れてしまい大事な要件を見逃してしまうことがあります。うっかりしないよう、以下のSlackの設定で予防できるようにしました。

  • Slackの /remind機能を利用し、タイムラインを見る頻度を減らす
  • 必ず見るChannelは通知が来るように設定し、見逃しを予防する
  • 定期的に見ないChannelはミュートし、見るべきものだけにフォーカスできるようにする

③ 1次情報をなるべくチェックする

タイムラインの閲覧を制限すると得られる情報に偏りがでてしまうため、議事録やメモなど、まとめられた資料を読んでキャッチアップしています。 コネヒトではDocBase(ドックベース)というドキュメント共有ツールを利用しているので、そちらに投稿されたものは毎日数分時間を取って読んでいます。

f:id:connehito:20170331142058p:plain

成果

上記3つを3ヶ月間実践した結果、3つの改善をすることができました。(333!)

1. 本質的な作業への消費時間が増えた

RescueTimeを見ると時間の使い方が大分良くなったことがわかります。
1日の作業時間のうち、本質的な作業に消費する時間が40%から64%と約1.5倍の改善を達成することができました。

f:id:connehito:20170330201425p:plain (本質的な作業 = Software Development + Design & Composition)

2. デザインの質が上がった

1issueに掛ける時間を増やしたことで、制作後の手戻り回数を減らすことができました。さらに 1スプリント内に消化できるストーリーポイントの合計が22ptから48ptと2倍近く増え、着手できるissueをかなり増やすことができました。(まぐれかもしれないので、引き続き邁進します)

f:id:connehito:20170330201435p:plain

3. 気持ちに適度な余裕ができた

時間に余白をつくることができたため、納品が近い成果物があった場合も気合いで解決することがなくなりました。切羽詰まるような不安要素がなくなり、健全な気持ちでモノゴトに立ち向かえるようになりました。

終わりに

生産性というテーマと向き合う中で、デザイナーとして一歩成長することができました。サービスのことを俯瞰して考える時間が増え、自分自身が納得感を持てるアウトプットを出せるようになったからです。
とはいえ、今回ご紹介した改善策は、"マイナスをゼロに戻すためのもの"だと考えています。
「生産性を高める」ということにおいて、良いアウトプットを出すためにはやっぱりスキル面を磨かないといけません。そのために、今後はゼロからプラスにするための改善に取り組んでいこうと考えています。最後まで読んでいただき、ありがとうございました!

参考

Kotlin1.1の新機能について

f:id:tommy_kw:20161106193050p:plain

こんにちは!@tommykwです。先日のDroidKaigiとても楽しかったですね。2日間参加させていただき、どのセッションも素敵なセッションで学びのある時間を過ごせました。スタッフ、スピーカー、参加者の皆さん、ありがとうございました!

さて、Kotlin1.1が3/1にリリースされました。早いものでもう3週間ほど経ちますが、すでに利用されている方も多くいらっしゃるのではないでしょうか。Kotlin1.1では多くの機能がリリースされましたが、今回はKotlin1.1で導入された便利な新機能をいくつか情報共有したいと思います。

今回紹介する新機能

  • Alt+Enter
  • _キーワード
  • 型エイリアス
  • ::キーワード
  • データクラス継承

Alt+Enter

Alt+EnterとはIntentionと呼ばれるもので、Android Studio上でAlt+Enterすることによって簡単にクイックフィックスできる機能です。 高速コーディングを支える技術で、Kotlin1.1で新しく追加されたクイックフィックスについて紹介したいと思います。

  • Add Android View constructors using @JvmOverloads

Android開発でカスタムViewを作る場合はコンストラクタを複数定義する必要があり、とても冗長になります。 それをKotlinの@JvmOverloadsを使うことによって簡潔に記述できます。 f:id:tommy_kw:20170317080730g:plain

簡単ですね。しかし、以下のように既にコンストラクタを定義した場合はクイックフィックスできません。 この状態からコード最適化できるとリファクタリングが容易です。

class CustomView: View {
    constructor(context: Context) : this(context, null) {
    }
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
    }
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
    }
}
  • Convert to range check

範囲指定しているif文をRange型に変換します。 f:id:tommy_kw:20170317082139g:plain

  • Merge 'if's

ネストしたif文をマージします。 f:id:tommy_kw:20170317082603g:plain

  • Use destructuring declaration

ラムダで分解宣言を使うことによって、オブジェクトからデータを取り出して別の変数に代入します。 f:id:tommy_kw:20170317224853g:plain

よく見ると、fun main(args: Array<String>) {} と記述するとKotlinアイコンが表示されますね。 エラー、警告表示があった時に取り敢えずこのショートカットを使えば役に立ちますし、特にカスタムViewのクイックフィックスは Kotlinっぽく記述できてコーディングが捗りそうです。

_キーワード

Swiftでもお馴染み_キーワードで、パラメータを省略するためのキーワードです。 ママリQにて、Kotlin1.1にアップグレードした際にLintで未使用な変数である旨の警告が大量に表示されました。そんな時に利用したのが_キーワードになります。使用用途としては以下の通り、ラムダの引数、無名関数の引数、分解引数で利用できます。

  • ラムダの引数
// v, hasFocusパラメータを定義する
view.setOnFocusChangeListener { v, hasFocus ->
}

// v, hasFocusパラメータを省略する
view.setOnFocusChangeListener { _, _ ->
}
  • 無名関数の引数
// xパラメータを定義する
val function = fun(x: Int) {}

// xパラメータを省略する
val function = fun(_: Int) {}
  • 分解引数 その1
// a, b, cパラメータを定義する
val (a, b, c) = listOf(1,2,3)

// bパラメータを省略する
val (a, _, c) = listOf(1,2,3)
  • 分解引数 その2
// a, b, cパラメータを定義する
listOf(Triple(1, 2, 3)).forEach { (a, b, c) -> }

// a, cパラメータを省略する
listOf(Triple(1, 2, 3)).forEach { (_, b, _) -> }
  • onClickイベントのunusedなView引数

AndroidでよくあるパターンのonClickイベントのView引数がunusedになる問題ですが、

fun onPhotoClick(@Suppress("UNUSED_PARAMETER") view: View) {
    // do something
}

残念ながら以下の様にメソッドの引数には適用できません。unusedを回避するには@Suppress("UNUSED_PARAMETER") アノテーションをつけるのが良さそうです。

fun onPhotoClick(_: View) {
    // do something
}
  • _キーワードに型を明示的に定義

Function型の引数が複数ある場合に{}{ _ -> }で記述するか悩むケースがあります。 以下はRxJavaを用いたサンプルになります。

// RxJava1の非同期Extension
fun <T> Observable<T>.async(onNext: (T) -> Unit, onError: (Throwable) -> Unit) {
    this.subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(onNext, onError)
}

// APIクライアントからメンバーカウントを取得する
apiClient.memberCount()
    .async(
        { result ->
            if (result.count > 0) {
                // do something
            }
        }, 
        { e ->  } // 未使用のeが未使用となる
    )

上記の{ e -> }{}に変えても良いですし、{ _: Thowable -> }と型指定した宣言をしても良いのではないでしょうか。 多少冗長になりますが、個人的には可読性が上がる場合もあるかなと思います。

apiClient.memberCount()
    .async(
        { result ->
            if (result.count > 0) {
                // do something
            }
        }, 
        { _: Thowable ->  }
    )

型エイリアス

型エイリアスは既存の型を別の型として提供でき、関数、コレクション、コンパニオンオブジェクト、ネストしたクラスなどに適用できます。 トップレベル宣言、メンバー宣言、ローカル宣言をすることができますが、Kotlin1.1ではトップレベル宣言のみサポートされています。

以下はKotlinのstdlibのコード内で型エイリアスを利用しているケースですが、本来importする必要があるライブラリを型エイリアスを使うことによって、利用する側はimport不要で利用出来るのが良いです。

TypeAliases.kt

@file:kotlin.jvm.JvmVersion

package kotlin.collections

@SinceKotlin("1.1") public typealias RandomAccess = java.util.RandomAccess


@SinceKotlin("1.1") public typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public typealias HashSet<E> = java.util.HashSet<E>


// also @SinceKotlin("1.1")
internal typealias SortedSet<E> = java.util.SortedSet<E>
internal typealias TreeSet<E> = java.util.TreeSet<E>

さらに、以下のAnnotations.ktを見ていただくといくつかのアノテーションをサポートしていることがわかります。 型エイリアスは@Deprecated, @SinceKotlin, @Suppressアノテーションを許可されていて、別名の型として利用する機会はあると思いますが、アノテーション付きで定義できるところが良いなという印象です。

Annotations.kt


@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith(""),
        val level: DeprecationLevel = DeprecationLevel.WARNING
)

@Target(CLASS, ANNOTATION_CLASS, PROPERTY, FIELD, LOCAL_VARIABLE, VALUE_PARAMETER,
        CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPE, EXPRESSION, FILE, TYPEALIAS)
@Retention(SOURCE)
public annotation class Suppress(vararg val names: String)

@Target(CLASS, PROPERTY, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public annotation class SinceKotlin(val version: String)

このような未定義を表したカスタムViewクラスも定義できますし、@Suppressアノテーションもうまく利用できるかもしれません。

@Deprecated("unused view")
typealias DeprecatedView = View

::キーワード

::キーワードが追加されました。これはJavaの<expression>::classメソッド参照と<expression>::<member name>メンバー参照をサポートしています。 毎回ラムダで少し冗長に記述するのではなく、::キーワードを使ってより簡潔に記述できます。

// ラムダ
listOf("a","b","c").map { it.toUpperCase() }

// メソッド参照
listOf("a","b","c").map(String::toUpperCase)
data class Member(val id: Int)
// ラムダ
intArrayOf(1,2,3).map { Member(it) }

// メソッド参照
intArrayOf(1,2,3).map(::Member)

また、KClassを取得するのに、.javaClass.kotlinがありましたが、::classを使ったほうがよさそうです。 以下のコードからもわかるように、.javaClass.kotlin::classでは動作が異なるため、.javaClassはDeprecatedになっていく予定のようです。

class Foo {
   fun bar() {
      val a: KClass = this.javaClass.kotlin
      val b: KClass = this::class
 
      a.memberProperties.forEach { property: KProperty1<Foo, *> ->
          property.get(this)
      }
 
      b.memberProperties.forEach { property: KProperty1<out Foo, *> ->
          property.get(this) //Error: Kotlin: Out-projected type 'KProperty1<out Foo,*>' prohibits the use of 'public abstract fun get(receiver: T): R defined in kotlin.reflect.KProperty1'
      }
   }
}

データクラス継承

データを保持するクラスとして便利なdata classがありますが、これは継承することができませんでした。以下の通り、Kotlin1.1からsealed classやopen classを継承できるようになり、柔軟なデータクラス設計ができるようになりました。

open class Base(
    open val id: Int, 
    open val name: String
)

data class Member(
    override val id: Int,
    override val name: String,
    val age: Int
) : Base(id, name)

val member = Member(1, "foo", 20)

今までは生成されるコードの一貫性を保つために、クラスを継承できない仕様になっていました。インターフェースは実装することができましたが、 sealed classやopen classを継承できるのはとても便利です。

Kotlin 1.0.7、1.1.1リリース

早いですね!1.0.7は現時点でバージョン1.1にアップグレードできないユーザが利用できるように、Gradleおよびアノテーション処理に関連する修正をバックポートできるバージョンです。1.1.1は1.1.0のバグ修正がメインで一部のIDE、Compiler、Librariesの修正を行っています。特に問題ないのであれば、1.1.1の利用をオススメします。

まとめ

Kotlin1.1の新機能について簡単に説明しましたが、いかがでしょうか。本来であれば、Kotlin1.1のメイン機能であるCorutinesについて情報共有できれば良かったのですが、正直まだ使いどころがわかっていません。こちらは引き続き理解を深めていきます。また今回紹介していない新しいスコープ関数provideDelegateオペレータなどすぐに利用出来る便利な機能がたくさんリリースされました。これらをうまく利用することで、Kotlinの言語機能を最大限に活かせそうです。それでは楽しいKotlinライフをお過ごしください!

Dangerで始めるPull Requestチェック自動化

f:id:Utmrer:20170228201418p:plain

こんにちはー!こねひとちほーのえんじにあのフレンズ@Utmrerだよー!

今回はPull Requestを自動でチェックしてくれるDangerについて紹介します。

Pull Requestでのコミュニケーション

Pull Requestのレビューは不具合の指摘やコーディングスタイルの統一、より良いコードのための提案などのために行われます。 ですが、次のようなコミュニケーションをしたことはありませんか…?

  • タイトルにIssue Idを含めてもらえますか?
  • WIPみたいなんですがレビューして大丈夫ですか?
  • Base branchが間違ってます、変更してください。
  • 変更履歴のdocsを更新してください。

このような「実装とは関係のない指摘」はできるだけ減らし、自動化したいものです。それを実現するのがDangerです。

Dangerとは

DangerのGitHubには次のように書かれています。

Formalize your Pull Request etiquette.

Stop saying “you forgot to …” in code review

つまり「Pull Requestの作法を形式化して指摘を自動化」するツールです。

DangerがチェックするのはPull Requestの体裁なのでそのPull RequestがSwiftのプロジェクトであろうがRubyであろうが関係なく、すべてのプロジェクトに導入可能です。 実際にRxSwiftというSwiftのプロジェクトやcapistrano、sentryなど様々なプロジェクトで導入されています。

Dangerの導入

Travis上でDangerを実行し、GitHubのPull Requestにコメントするところまでを説明します。

Gemfileの設定

GemfileにDangerを追加します。これだけでTravis上でDangerを実行できます。

source 'https://rubygems.org'

gem 'danger'

Dangerfileの追加

プロジェクトのルートディレクトリにDangerfileというファイルを追加します。

このファイルにDangerにどのような項目をチェックしてもらうかを記述していきます。

warn('あぶないよー!')

warn(message)と書くと「これは注意が必要だよ」とGitHubにコメントします。

通常は特定の条件によってコメントするのでifなどで囲みますが今回はこのままにしておきます。

.travis.ymlの設定

.travis.ymlにDangerを実行するように設定します。

cache:
  - bundler
before_script:
  - bundle exec danger

Cache機能を使えば毎回bundle installを実行する必要が無いので時間節約のためにおすすめです。

Travis環境変数の設定

TravisからGitHubへコメントするため、TravisにGitHubのアクセストークンを設定する必要があります。 アクセストークンはこちらのページから作成できます。

Dangerでは公開プロジェクトではpublic_repo、非公開プロジェクトではrepoの権限を持ったアクセストークンが推奨されています。

また、この時にアクセストークンを発行するGitHubアカウントは普段使っている個人アカウントではなくBot用のMachineアカウントの方が良いです。GitHubは1人1アカウントのMachineアカウントの作成を許可しています

発行したアクセストークンはTravisの各Repositoryのsettingsページで設定します。 f:id:Utmrer:20170228161943p:plain Environment VariablesDANGER_GITHUB_API_TOKENというNameで先程のアクセストークンを追加します。

Pull Requestの作成

以上でDangerの最低限の設定は終了です。Pull Requestを作成するとDangerからコメントが来ます。 f:id:Utmrer:20170228200733p:plain

ここまでの導入方法はDangerの公式HPにも載っています。 GitLabを利用している方やCircle CIを利用している方はこちらからご自分の環境にあった方法で導入してください。

構文

DSL

Dangerにはコメントを書き込むDSLがいくつかあり、最もよく使うであろう3つのDSLを紹介します。

📖 message

messageは単純にコメントを書き込むDSLです。定型文を返す時などに使えそうですね。

message('Pull Requestありがとうございます。レビューまで少々お待ち下さい。')

⚠ warn

warnは出来れば修正して欲しい、もしかしたら違反しているかもという場合に使います。Pull Requestのタイトルやディスクリプションに関するものはwarnがいいでしょう。

warn('descriptionを300字以上書いて下さい')

🚫 fail

failは許容できない内容の場合に使いましょう。このDSLを利用した時、Commit stateはfailureになります。

fail('LICENSEファイルは変更しないで下さい。')

変数

環境によってDangerfile上で扱える変数がいくつかあります。これらを組み合わせることでルールを作っていきます。今回はGitHub環境で使えるgitgithubについて紹介します。

git

gitという変数にはPull Requestの対象となっているCommitの情報などが含まれています。例えば「編集したファイルの一覧」「削除した行数」などです。

if git.deleted_files.include? "LICENSE"
  fail('LICENSEファイルは変更しないで下さい。')
end

github

githubという変数からPull Requestの情報が取得できます。github.pr_jsonからPull Request全体の情報を取得でき、GitHubのAPI Referenceなどを読むとgithub.pr_jsonにどのような値が入っているかわかるでしょう。

よく使うデータには専用の関数が用意されていてgithub.pr_titlegithub.pr_bodyからタイトルやディスクリプションがをシンプルに取得できます。

if !github.pr_json['mergeable']
  fail('マージできません。')
end
if !github.pr_title.include? "WIP"
  warn('このPull Requestは実装途中です。')
end

詳しくはReferenceをご覧ください。

Danger導入事例

Dangerの導入方法がわかったところで、OSSプロジェクトがDangerでどのようなことを行っているか見てみましょう。

capistrano

capistranoではDangerfileで次のようなことをチェックしているようです。

has_lib_changes = !git.modified_files.grep(/^lib/).empty?
has_test_changes = !git.modified_files.grep(/^(features|spec)/).empty?
has_changelog_changes = git.modified_files.include?("CHANGELOG.md")
if has_lib_changes && !has_test_changes
  warn("There are code changes, but no corresponding tests. "\
       "Please include tests if this PR introduces any modifications in "\
       "#{project_name}'s behavior.",
       :sticky => false)
end

「Libraryに変更があったならテストを書こう」ということをチェックしていますね。Reviewerに指摘される前にTestが書ければコミュニケーションが減って時間の節約になります。

if !has_changelog_changes && has_lib_changes
  markdown <<-MARKDOWN
Here's an example of a CHANGELOG.md entry (place it immediately under the `* Your contribution here!` line):
 ```markdown
* [##{pr_number}](#{pr_url}): #{github.pr_title} - [@#{github.pr_author}](https://github.com/#{github.pr_author}).
 ```
MARKDOWN
  warn("Please update CHANGELOG.md with a description of your changes. "\
       "If this PR is not a user-facing change (e.g. just refactoring), "\
       "you can disregard this.", :sticky => false)
end

またLibraryに変更があったならCHANGELOG.mdに変更点を書くように促しています。これは商用プロダクトでもHelpの更新や仕様書の更新などに使えそうなロジックです。

capistrano/Dangerfile

Sentry

SentryではPull Requestの体裁に関するものがいくつかあります。

# Make it more obvious that a PR is a work in progress and shouldn"t be merged yet
warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" || github.pr_body.include?("#wip")

タイトルとディスクリプションをチェックしてPull RequestがWIPかどうかを判定しています。WIPなのにReviewをお願いすることを防げますし、Reviewerも「WIPなので見なくて大丈夫!」と判断できます。

@S_BIG_PR_LINES ||= 500
# Warn when there is a big PR
warn("Big PR -- consider splitting it up into multiple changesets") if git.lines_of_code > @S_BIG_PR_LINES

Pull Requestの実装行数が大きすぎる場合は分割するように促しています。実装内容が膨大なPull RequestはReviewが大変ですし、不具合が紛れ込む可能性も高いので分割してPull Requestを作るのは良いことですね。

sentry/Dangerfile

mamariQ

最後に、弊社のママリQ iOSアプリのDangerfileを公開します。

# --------------------
# test
# --------------------
is_source_changed = !git.modified_files.grep(/^mamariQ\/Classes/).empty?
is_test_changed = !git.modified_files.grep(/^mamariQTests\/Case/).empty?

if is_source_changed && !is_test_changed
  warn('コードの変更がありますが、テストの変更がありません。必要であればテストを追加・修正しましょう。')
end

# --------------------
# base branch
# --------------------
is_to_master = github.branch_for_base == 'master'
is_to_release = !!github.branch_for_base.match(/release-[0-9]+\.[0-9]+\.[0-9]/)
is_from_release = !!github.branch_for_head.match(/release-[0-9]+\.[0-9]+\.[0-9]/)

if is_to_master && !is_from_release
  warn('masterへmerge出来るのはrelease branchのみです。')
end

if is_to_release
  warn('release branchに対してPRを向けないで下さい。develop branchに向けてPRを作成し、develop branchをrelease branchにmergeしてください。')
end

# --------------------
# pr title
# --------------------
is_wip = github.pr_title.include? '[WIP]'

if is_wip
  warn('このPRは作業中です。')
end

is_to_develop = github.branch_for_base == 'develop'
is_start_with_issue_id = !!github.pr_title.match(/^#[0-9]+/)

if is_to_develop && !is_start_with_issue_id && !is_wip
  warn('PRのタイトルは<b>「#XXX Foo bar...」</b>とIssue idから始めてください。')
end

# --------------------
# diff size
# --------------------
is_big_pr = git.lines_of_code > 500

if is_big_pr
  warn('PRの変更量が多すぎます。可能であればPRを分割しましょう。分割が難しければ次から気をつけるようにしましょう。')
end

弊社のiOS開発ではgit-flowを採用していて、Pull RequestにおけるBase branchの選択がちょっと複雑です。そのため、DangerでBase branchのチェックを自動化しています。このDangerfileを書いたのは私なのに自分で引っかかることがあってDangerに感謝感激です。

人は人にしか出来ないことを

DangerにはPlugin機能があり、機能を拡張できるので可能性は∞です。Android LintSwiftLintのPluginもあります。(私の環境ではSwiftLint Plugin動かなかったので動作は保証できないですが…)

そういえば、Dangerを作ったOrtaさんKrauseFxさんはtry! Swiftで講演されるようですよ。try! Swiftに行かれる方はDangerの話もしてみてはどうでしょう?私もいきます☺

機械に出来ることは機械に任せて、人は人にしか出来ないことに集中していきましょう。

Slackで簡単チェック!textlint で始める文字校正Bot

f:id:fortkle:20170216212251p:plain

こんにちは! 新オフィスに移転してから毎日2缶ドクターペッパーを買うのが習慣化してきた @fortkle です。

今回はSlackで簡単に文字校正ができるBotを作ってみたのでご紹介したいと思います。

手軽に校正したい

メールやチャットだけでなく、こういったブログやissueに書く仕様など「文章を書く」シーンはたくさんあると思います。

そんなとき自分が伝えたいことを相手にちゃんと伝えることができれば良いのですが、良い文章を書くのはなかなか難しく、今も試行錯誤しています。

そこで、相手に伝えたいことがちゃんと伝わる良い文章を書くためにSlack上で簡単に試せる文字校正Botを作ってみました。

成果物

実際に見たほうが早いと思うので画像を交えてご紹介します。

例として以下の文章をチェックしてみたいと思います。

この文章はご存知の通り、ダミーテキストです ヽ(^o^)丿
そして、様々な、エラーチェックに、引っかかる文章になっています。そして、意味はありません。
この文章が書くのが自動ではなく手動なのが地味に大変だったところです。
いや、大変だったとはいえないこともないです。このBOTはナウでヤングなやり方で
文章をチェックすることができます。チェック結果はslackで即座に見れます。
焦ってしまうとと誤字をすることがありますよね。そんなときに便利です。

チェックするにはSlackで文字校正Botに「check」から始まるDMを送るだけです。

check
ここにチェック
したい文章を
書きます。

実際に校正した結果がこちらです。

f:id:fortkle:20170216183734g:plain

1行目:ご存知 => 用字(ご存じ)
1行目:(^o^) => 顔文字が使われています。
2行目:様々 => 用字(さまざま)
2行目:エラーチェック => 半角カタカナが使われています。
2行目:同じ接続詞が連続して使われています。
3行目:一文に二回以上利用されている助詞 "が" がみつかりました。
4行目:こともない => 二重否定()
4行目:ナウ => 死語が使われています。
4行目:ヤング => 死語が使われています。
4行目:一文に二回以上利用されている助詞 "で" がみつかりました。
5行目:することができます => 冗長表現(できます)
5行目:見れ => ら抜き(見られ)
5行目:slack => 全角英数字が使われています。
6行目:"と" が連続して2回使われています。

どうでしょうか?意外と使えそうな気がしませんか! プロが使うような有料の校正ツールに比べると指摘内容は少ないですが、このように簡単に文字校正を試すことができます。

文字校正Botのメリット

誰でも簡単に使える!

まだ個人でしか使っていませんがもし会社で使うとなった場合でも、有償の文字校正ツールと違いSlackさえあれば誰でも利用できる手軽さがあります。 また、校正したいテキストをBotにDMで投げる形なのでチャンネルを汚すことなく利用できます。

自分が用意した辞書でカスタマイズできる!

実際の校正結果の画像をよく見ていただくと分かると思いますが、「顔文字」や「死語」などの指摘があるかと思います。 これらはどちらも自分で用意したオリジナルのルールです(死語はジョークルールですよ!)。 後述する prh というツールを使うと自分が用意した辞書を使って文章を校正することが出来ます。

校正内容のメンテナンスに夢が広がる!

今回は未実装ですが、実装さえすれば文字校正をするだけでなく校正内容も管理できると思います。

たとえば ng-word add [追加したい文言]ng-word remove [削除したい文言] などCRUD操作をできるようにしておけば Slack上から簡単に、しかも非エンジニアだけで校正内容をメンテナンスすることができそうですね!

仕組みの解説

今回の文字校正Botは Botkit をベースに textlintというツールを使ってSlack上での校正を実現しています。 Botkitのセットアップが終わりBotが動いている状態からスタートし、実際に文字校正ができるようになるところまでの実装手順をご紹介します。

1. 使っているツール

textlint

textlintは、@azu さんが作っている自然言語をLintできるツールです。JavaScriptにおけるESLintのように、あらかじめ設定しておいたルールに基づいて、テキスト上の間違いや読みにくい点などを指摘してくれます。

プラガブルであることが特徴でデフォルトでは1つもルールがありません。そのため、一般公開されているルールや自分で作ったルールを組み合わせる必要があります。

一般公開されているルールは こちら(Collection of textlint rule) で確認できます。 今回は校正のルールとして下記6つを採用しました。

採用ルール

2. インストール

textlint本体とルールをnpm経由でインストールしましょう。ここではyarnを使ってインストールします。

$ yarn add textlint textlint-rule-ja-yahoo-kousei textlint-rule-prh textlint-rule-no-doubled-conjunctive-particle-ga textlint-rule-no-doubled-conjunction textlint-rule-no-doubled-joshi textlint-rule-ja-no-successive-word

3. prh で使う辞書の作成

辞書はyaml形式で下記のように書くことができます(基本的な書き方はこちら を参考にしてください)。

version: 1
rules:
  # 大文字小文字全角半角の統一
  - expected: Cookie
  # 以下と等価 正規表現には強制でgフラグが付く
  # - expected: Cookie
  #   pattern: "/[CcCc][OoOo][OoOo][KkKk][IiIi][EeEe]/g"

  # 表現の統一を図る
  - expected: デフォルト
    patterns:  ディフォルト

さて、この設定ファイルを見ると分かる通り本来prhは表記ゆれを検出するためのツールですが、「独自の辞書を作れること」「エラーメッセージを辞書毎に設定できること」の2点から今回は簡易的なNGワードチェックのために使ってみることにします。本来の用途とは異なりますが、 expected に「エラーメッセージ」を書き、 patternにエラーにしたい単語を列挙します。

version: 1 
rules: 
  # 本来、expectedには「期待される値」が書かれるべきだがtextlint-rule-prhがprhフィールドに対応していないので「エラーメッセージ」を書く 
  - expected: 死語が使われています。 
    patterns: 
      - アイアム・ソーリー、ひげそーりー
      - あたりまえだのクラッカー 
      - アベック 
      - いただきマンモス 
      - うれピー 
      - おいくら万円 
      - オッケー牧場
      - ナウ
      - ヤング
      # (略)

4. textlint の設定ファイルを作成 ( .textlintrc )

次にtextlint の設定ファイルをJSON形式で作成し、 .textlintrc という名前で保存します。 .textlintrc には読み込みたいルールと設定を記述します。

{ 
  "rules" : { 
    "no-doubled-conjunctive-particle-ga": true,
    "no-doubled-conjunction": true,
    "no-doubled-joshi": true,
    "ja-no-successive-word": true,
    "ja-yahoo-kousei": true,
    "prh": {
      "rulePaths": [
        "./dict/prh.yml"  ← ここでprhで使う辞書を読み込む
      ] 
    } 
  }
}

ja-yahoo-kouseiルールを利用するためには事前にYahoo アプリケーションIDを取得する必要があります。 詳しくはREADMEを見てみてください。

5. textlintをモジュールとして使う

公式のドキュメントを参考に実装します。 実装例としてサンプルコードを載せておきます。

import path from 'path'
import {TextLintEngine} from 'textlint'

export default function(controller) {

  const engine = new TextLintEngine({
    configFile: path.join(__dirname, '../.textlintrc'),
  })

  const formatResults = (results) => {
    let output = '```\n'
    const messages = results[0].messages
    for (let i = 0; i < messages.length; i++) {
      const msg = messages[i]
      const msgText = `${msg.line}行目:${msg.message}\n`
      output += msgText
    }
    output += '```'

    return output
  }

  controller.hears([/^check[\n\r]([\s\S]*)/], ['direct_message'], (bot, message) => {
    const msgBody = message.match[1]

    engine.executeOnText(msgBody).then(results => {
      if (! engine.isErrorResults(results)) {
        bot.reply(message, 'エラーは見つかりませんでした。')
        return
      }

      let output = ''
      output += 'エラーが見つかりました。\n\n'
      output += formatResults(results)

      bot.reply(message, output)
    })
  })
}

これで実装は完了です!簡単なのでぜひ試してみてください!

最後に

さて、今回は textlint を使った文字校正Botについてご紹介しました。

textlintはまだまだ発展途上にあるため実務でがっつり使うにはまだ少し時間がかかるかな、というのが実際に使ってみた所感です。 特に用字用語や送り仮名、誤字脱字の検出に関しては辞書ベースの検出になるためどうしても力不足であることは否めません。 Yahoo! 校正APIを使うことである程度カバーできますが、textlintのルールは誰でも簡単に作ることができるので今後そのあたりをカバーするルールが充実するといいなと思いました。

もし興味があればルール作りなどにも挑戦してみてください!よいBotライフを!

非ネットワークエンジニアが社内ネットワークを構築したときに解決した3つの課題

f:id:tatsushim:20170203145647j:plain

こんにちは!

CTOの島田(@tatsushim)です。弊社は2017年から新オフィスに移転しました!

新オフィス移転に際して、必要になってくるのが新しい社内ネットワークです。 しかし弊社にはネットワークを専門に扱うメンバーがいません。 一方で移転することを決めてから3ヶ月というタイトなスケジュールの中でオフィス移転を実現する必要があったため、 自分たちで構築することにしました。

移転前のオフィスにおける3つの課題

1. Wi-Fiがたまに切れることがある

1つのネットワーク機器にアクセスが偏ったり、周波数帯が近いWi-Fiが存在して混線していたのが原因と考えられます。

2. Wi-Fiが遅くなることがある

Kitting*1でイメージファイル等の重いファイルのダウンロードが行われると、同じWi-Fiに接続している他の端末のネット速度が遅くなっていました。

3. いちいち場所を移動する度にWi-Fiを切り替える必要があった

AirMacを利用していたのですが、ブリッジ接続が上手くいかず、会議室と執務スペースでそれぞれ別のSSID*2で運用されていたために、移動した際に接続の切り替えを忘れていてネットに接続できずイライラ・・・なんてこともありました。

3つの課題に対する解決策

1. ネットワーク機器の適切な配置と周波数帯の設定

f:id:tatsushim:20170203122130p:plain こちらの図は、弊社の実際のオフィスの見取り図の上に各ネットワーク機器とそのカバー範囲をビジュアライズしたものです。 六角形の中心がネットワーク機器で、その機器からある程度均等な距離でWi-Fi接続をカバーする形にしました。 3つの周波数帯を用い、隣接するネットワーク機器の周波数帯を同じものにしないことで混線を避けました。

また、ネットワーク機器としてはAerohiveを導入しました。 Aerohiveを選定した理由は、グループ会社で既に安定して動いていた実績があったからです。

2. Kitting用の回線を作成

用途別に、3つの回線を用意しました。

アドレス 用途
固定IP① 社内用
固定IP② Kitting
DHCP割当 ゲスト

社内用とKitting用の回線を物理的に別にすることでKitting中でも速度低下などの影響を受けない環境になりました。

3. SSIDを統一

今までは執務室がConnehito Network1, 会議室がConnehito Network2という形でSSIDが複数ありましたが、 Aerohiveのローミング機能を利用して、上記の図の機器間で1つのSSIDを共有することができました。

実際に移転してみて

年明け初日から新オフィスでの営業でしたが、始業日から通常通り業務を始めることができ、ネットワークに関するトラブルはおきていません。 会社でインターネットが快適に使えるという“あたりまえ” を無事に提供することができたことは1つの成果なのではないかなと思っています。

移転作業に関わってくださった方々へ本当に感謝

今回の移転作業は3ヶ月という短い準備期間の中でメンバーやSyn.Holdingsの方々の助けもあり無事に達成することができました。 改めて関わってくださった皆様に感謝を申し上げます。

というわけで

快適なネット環境も手に入れたのであとはユーザーのためにプロダクトに向かうだけですね!今年も愚直に、粛々と頑張ります。 島田(@tatsushim)がお送りしました。

*1:初期環境構築に必要なソフトウェアのインストール

*2:無線LANにおけるアクセスポイントの識別名