パステル色な日々

気ままに綴るブログ

Pythonでデータ分析する準備をし〼。(Numpy激闘編)

データ分析前の準備体操。

Pythonを使うデータ分析

普段データ分析といってもいきなりPythonを使うことはしません
大抵はSQLにより取得できるデータをスプレッドシートへ吐き出し、datastudioを使って可視化することで顧客には十分な価値が提供できます
しかし、精度の向上や独自の仮説構築から検証に至るような過程でPythonを使ったデータ分析が必要になってきます
ちょうど僕も必要になるフェーズになったので今回はまずPython入門としてNumpyの学習に努めて見ます

NumPy入門

  • ベクトル生成
  • 行列生成
  • ベクトルから行列の生成
  • 行列のサイズ評価
  • 行列の演算
  • 配列の操作
    • スライシング
    • インデクシング
    • ブロードキャスティング

行列の転置を忘れていたので思い出す。行列の掛け算もわかりませんでした
学生時代に習ったことを忘れていたのがかなりショック...
面白かったのはNumpyをつかって生成した行列を掛ける際に dot という関数を使うのですが、これを使わずに掛け算を試みると対応したi行j列をかけ合わせたものになります。
おそらくこれは行列に行列を掛けるブロードキャスティングではないでしょうか

>>> a
array([[1, 2],
       [3, 4]])
>>> b
array([[5, 6],
       [7, 8]])
>>> a*b
array([[ 5, 12],
       [21, 32]])

スライシングをした際にインデックスが2以上2未満の指定で呼び出してみるとからの配列が返ってくるような感じです

>>> a
array([[1, 2],
       [3, 4]])
>>> a[0:2]
array([[1, 2],
       [3, 4]])
>>> a[2:2]
array([], shape=(0, 2), dtype=int64)

ところが存在しない行を取り出そうとすると、エラー扱いとなります

>>> a
array([[1, 2],
       [3, 4]])
>>> a[1,:]
array([3, 4])
>>> a[2,:]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index 2 is out of bounds for axis 0 with size 2

特に行列の結合結果が面白いですね。 c__r を使った結合結果がひと目では理解できませんでした。

>>> np.r_[np.arange(3),np.arange(3)]
array([0, 1, 2, 0, 1, 2])
>>> np.c_[np.arange(3),np.arange(3)]
array([[0, 0],
       [1, 1],
       [2, 2]])

r_ は行列を縦に結合するものであるはずにも関わらず。横に結合されたように見えます。
縦に結合されると6行1列の配列を期待します。
array([[0], [1], [2], [0], [1], [2]]) これの結果の簡略と考えられそうです。
であるのであれば1次元配列からその配列が3行1列なのか1行3列であるのか見分けがつきそうにないですね。

まずは、Numpyによる行列演算について見ていきました。
疑問については仲間に助けてもらいながら教えてもらいます。次回はSciPy。

関数型言語らしい書き方をElmのコードで知る

覚えたての関数型言語でコードを書いてみると何故か関数型っぽくないなーと感じることがしばしばあったので、どう考えて書いていけばよいのか調べてみました。ここでは書籍 関数型プログラミング実践入門 よりトップダウンに考える方法をElmで紹介しようと思います。

対象者

最近、Elmを始めたがいまいち関数型言語らしい書き方がわからない方

わかること

書籍で書かれているように詳しく過程は紹介しません。 それでは始めましょう。

ランレングス圧縮

今回の問題は書籍で紹介されているランレングス圧縮を扱います。 詳しい説明はWikipediaを見ていただくとして、ここでは具体例だけをお見せいたします。

AAABBCCCCAAA -> A3B2C4A3

この通り、ランレングス圧縮とはこのような符号化のことを言います。

命令形言語のエッセンスで解く

まずはこちらをご覧ください。

module RLE exposing (rle)


-- >>> rle ""
-- ""
-- >>> rle "A"
-- "A1"
-- >>> rle "AAABBCCCCAAA"
-- "A3B2C4A3"

rle : String -> String
rle rawString =
    case String.toList rawString of
        [] ->
            ""

        h :: t ->
            aux 1 h (String.fromList t) -- 最初の一文字を覚える


aux : Int -> Char -> String -> String
aux runLength prevChar tailString =
    case String.toList tailString of
        [] ->
            String.concat <| String.fromChar prevChar :: [ toString runLength ] -- 文字がなくなったらおしまい

        c :: s ->
            if c == prevChar then
                aux (runLength + 1) prevChar (String.fromList s) -- 同じ文字なら連長をカウントアップ
            else
                String.concat <|
                    String.fromChar prevChar
                        :: [ toString runLength, aux 1 c (String.fromList s) ] -- 違う文字なら新たに1からカウント

書籍で紹介されているものをElmで書いたものです。forループを再帰で解いた感じがします。 しかし、これだけではどこが関数型言語らしくないのかわかりません。

関数型言語のエッセンスで解く

それでは次に関数型言語のエッセンスで解いてみます。 コードからトップダウンに問題を考えるってこういうことかーと感じていただければと思います。 文字列から文字列への変換の型を満たす関数を考える前に中間構造の型を考えてみましょう。

module RLE exposing (rle)


rle : String -> String
rle =
    fromCharAndRunLength << toCharAndRunLength
    
    
-- >>> fromCharAndRunLength [("A", 3), ("B", 4)]
-- "A3B4"
fromCharAndRunLength : List (Char, Int) -> String
fromCharAndRunLength =
    cat << rls2strs


-- >>> rls2strs [("A", 3), ("B", 4)]
-- ["A3", "B4"]
rls2strs : List (Char, Int) -> List String
rls2strs rls =
    ["P1"]


-- >>> cat ["A3", "B4"]
-- "A3B4"
cat : List String -> String
cat strs =
    "P1"


-- >>> toCharAndRunLength "AAABBBB"
-- [("A", 3), ("B", 4)]
toCharAndRunLength : String -> List (Char, Int)
toCharAndRunLength s =
    [('P', 1)]

Elm Search

Elm Searchを使うと型から関数を見つけることが出来ます。 すると String.concat 関数が見つかるので cat を置き換えてみましょう。 hoogleではないものの、Elmにもこういうサービスが有ったんですね。

module RLE exposing (rle)

import List.Extra exposing (group)


rle : String -> String
rle =
    fromCharAndRunLength << toCharAndRunLength
    
    
-- >>> fromCharAndRunLength [("A", 3), ("B", 4)]
-- "A3B4"
fromCharAndRunLength : List (Char, Int) -> String
fromCharAndRunLength =
    String.concat << rls2strs


-- >>> rls2strs [("A", 3), ("B", 4)]
-- ["A3", "B4"]
rls2strs : List (Char, Int) -> List String
rls2strs =
    List.map rls2str
    
    
rls2str : (Char, Int) -> String
rls2str (c, n) =
    String.concat <| String.fromChar c :: [ toString n ]


-- >>> toCharAndRunLength "AAABBBB"
-- [("A", 3), ("B", 4)]
toCharAndRunLength : String -> List (Char, Int)
toCharAndRunLength =
    List.map toPair << group << String.toList


toPair : List Char -> (Char, Int)
toPair rls =
    case rls of
        h :: _ ->
            ( h, List.length rls )

        [] ->
            Debug.crash ""

String -> List String をElm Searchで検索しても見つかりませんが、 List a -> List (List a) で検索してみると List.Extra.group が見つかります。 HaskellではChar型のリストがString型と考えるみたいでして、Elmでも同様に考えてみます。

さて、続けて toPair を実装してみましたが、よく見てください Debug.crash が登場しています。ロジック上これが呼ばれることはないはずですが、ここではきっちりとプログラム実行中に停止しないようにしてみます。

module RLE exposing (rle)

import List.Extra exposing (group)


rle : String -> String
rle =
    String.concat << List.map (rls2str << toPair) << group << String.toList


rls2str : Maybe ( Char, Int ) -> String
rls2str rls =
    case rls of
        Just ( c, n ) ->
            String.concat <| String.fromChar c :: [ toString n ]

        Nothing ->
            ""


toPair : List Char -> Maybe ( Char, Int )
toPair str =
    case str of
        h :: _ ->
            Just ( h, List.length str )

        [] ->
            Nothing

fromCharAndRunLengthtoCharAndRunLength はもう不要なので削除します。これで完成です。

どのあたりが関数型言語らしいコードなのか

トップダウンに問題を考えていき、機械的に型を解決していくことで解決まで出来ました。 前者と見比べてみると高階関数と関数合成を多様していることがわかります。 これはボトムアップな思考のまま逐次処理を行うコーディングをしていると見られない関数型言語らしい記述らしいです。 高階関数トップダウンに問題を考える上で非常に相性が良いとされていました。

やってみたものの果たしてElmでこの解き方が推奨されるのかはわかりませんが、こういう書き方もできるというご紹介でした。

繰り返しになりますが、本投稿は書籍 関数型プログラミング実践入門に紹介されている考え方を踏襲してElmで実装しています。本書にはより詳しく考え方が載っているのでぜひご一読ください。

また、今回使用してコードは次のリポジトリで公開しております。

https://github.com/pastelInc/elm-run-length-encoding/blob/master/src/RLE.elm

Elmでもテストを書こう

Elmでテストを書きましょう。
この文章はElm2 Advent Calendar 2017の22日目の投稿です。 昨日は @cyclone_t さんの YouTube IFrame Player APIをElmから利用する でした。

対象者

Elmのテストに興味がある人が対象になります。
ある程度、他言語でユニットテストを書いたことがある方が望ましいです。

わかること

  • Elmのテスティングライブラリはどんなものか
  • ElmのテストをCLI上で行うにはどうするのか
  • 文字列の一致をテストするElmのテストコードの書き方

それでは始めましょう

Elmのテスティングライブラリはどんなものか

Elmには他の言語同様にテストをサポートするライブラリが用意されています。

elm-community/elm-test

suite : Test
suite =
    describe "The String module"
        [ describe "String.reverse" -- Nest as many descriptions as you like.
            [ test "has no effect on a palindrome" <|
                \_ ->
                    let
                        palindrome =
                            "hannah"
                    in
                        Expect.equal palindrome (String.reverse palindrome)

            -- Expect.equal is designed to be used in pipeline style, like this.
            , test "reverses a known string" <|
                \_ ->
                    "ABCDEFG"
                        |> String.reverse
                        |> Expect.equal "GFEDCBA"

            -- fuzz runs the test 100 times with randomly-generated inputs!
            , fuzz string "restores the original string if you run it again" <|
                \randomlyGeneratedString ->
                    randomlyGeneratedString
                        |> String.reverse
                        |> String.reverse
                        |> Expect.equal randomlyGeneratedString
            ]
        ]

elm-testがElmにおけるテスティングフレームワークです。テストコードに必要なモジュール一式が提供されています。特徴的なのはファジングを気軽に実行できることです。

ファジングとは、ソフトウェアの不具合(とくに脆弱性を意図することが多い)を発見するためのテスト手法の一つである。ファズ(英:fuzz)(予測不可能な入力データ)を与えることで意図的に例外を発生させ、その例外の挙動を確認するという方法を用いる。ファズテストと呼ばれることもある。 ファジング - Wikipedia

Fuzzモジュールはファジングを強力にサポートします。seedからランダムな入力データを自動で生成してテスト対象コードの入力とすることが可能です。

Fuzz - elm-test 4.2.0

String型の生成データを例に見ても "", "\n" のような罠となりそうな文字列も生成してくれます。入力を自前で用意しないといけない手間が省ける点でも利用をおすすめできます。

詳しくは Elm2 Advent Calendar で @jinjor さんが記事をかかれています。要チェックですよ!

[Elm] Fuzzer でテストデータを量産しよう

ElmのテストをCLI上で行うにはどうするのか

Elmのテストコードはブラウザ上で結果を確認する方法とコンソール上で確認する方法が存在します。CIの都合上、一般的にはコンソール上で確認するシーンが多いと思いますのでコンソール上で実行するためのテストランナーをご紹介します。

rtfeldman/node-test-runner

node-test-runnner はテストの実行をサポートするだけではなく、初期テスト環境を自動構築してくれます。

$ elm-test init
Created ./src
Created ./elm-package.json
Created ./tests/elm-package.json
Created ./tests/Example.elm
Created ./.gitignore
Starting downloads...

  ● eeue56/elm-lazy-list 1.0.0
  ● eeue56/elm-lazy 1.0.0
  ● eeue56/elm-shrink 1.0.0
  ● elm-community/elm-test 4.2.0
  ● elm-lang/html 2.0.0
  ● elm-lang/virtual-dom 2.0.4
  ● mgold/elm-random-pcg 5.0.2
  ● elm-lang/core 5.1.1

Packages configured successfully!

テストコードが書けたらテストを実行しましょう

$ elm-test
Success! Compiled 0 modules.                                        
Successfully generated /dev/null
Success! Compiled 1 module.                                         
Successfully generated ./elm-stuff/generated-code/elm-community/elm-test/elmTestOutput.js

elm-test 0.18.10
----------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 96181054


TEST RUN INCOMPLETE because there is 1 TODO remaining

Duration: 242 ms
Passed:   0
Failed:   0
Todo:     1
↓ Example
◦ TODO: Implement our first test. See http://package.elm-lang.org/packages/elm-community/elm-test/latest for how to do this!

テスト結果に注目してください。Todoがあるのがわかると思います。これはテストコード内にtodoを設定しておくことができるAPIが提供されているためです。 それでは実際にテストコードを書いてみましょう

Elmのテストコードの書き方

elm-test init で作成された Example.elm を少し変更してみます。

module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)


suite : Test
suite =
    describe "Example Test Suite"
        [ test "should equal" <|
            \() ->
                description
                    |> Expect.equal "Elm is functional language."
        ]

description : String
description =
    "Elm is functional language."

これは文字列 Elm is functional language. の一致をテストしています。
describeTest モジュールが提供しています。これを利用するとテストの名前をつけることができます。ここで定義しておくとテスト失敗時にどのテストケースが失敗したのかがわかりやすくなるので設定をおすすめします。第二引数がリストになっているのは複数のテストケースを渡すことができるからです。テストケースは test で実行しています。第一引数でテストケースに名前をつけています。第二引数でテストの詳細を実装します。実際には関数の実行結果をテストすることになります。

elm-test 0.18.10
----------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 1753188457

↓ Example
↓ Example Test Suite
✗ should equal

    "Elm is functional language."
    ╷
    │ Expect.equal
    ╵
    "Elm is functionall language."



TEST RUN FAILED

Duration: 261 ms
Passed:   0
Failed:   1

テストをわざと失敗させてみました。するとこのように失敗したテストの詳細がターミナル上に出力されます。

等価性を判定する Expect.equal の他にも様々なAPIが提供されています。

Expect - elm-test 4.2.0

より実践的なテストコードの書き方は node-test-runner のexampleコードが参考になります。

node-test-runner/TestsFailing.elm at master

このテストコードでは先に説明したFuzzモジュールを使ったテストも体系的に学ぶことができるので、 git clone コマンドを実行して是非手元で試してみてください。

まとめ

  • elm-testを使おう
  • node-test-runnerを使ってテストを実行してみよう
  • Expectモジュールが提供するAPIを使ってテストコードを書こう

明日は @zaneli@github さんの Elm + native moduleでドラッグ&ドロップしたファイルの情報を取得する です!

フロントエンジニアとしてUXに触れる取っ掛かりとしてのコンテキスト

この文章はFringe81 Advent Calendar 2017の7日目の投稿です。 また、この文章で述べる考えはUX DAYS TOKYO主催のワークショップ、コンテキストの理解と実践に基づいております。

対象者

  • フロントエンジニアだけどUXという言葉しか知らない方

わかること

  • コンテキストはUXの一端を担っている
  • コンテキストの種類

それでは始めましょう

UXって何?

はじめにこの文章中でのUXについて定義してみます。 この文章中でのと断ったのには理由があります、UXという言葉は色んなシーンや人によって解釈されていますが、決まりきった定義がないと私が考えているからです。 気になる方は情報を収集してみてください。山ほど資料が見つかるはずです。

UX = ユーザーのタスク + コンテキスト

今回はこのようにUXを解釈します。みなさんがサービス開発者であればユーザーは何らかの目的をもってサービスを利用するはずです。ここで言うところのユーザーのタスクとはこのことを指しています。ユーザーのタスクとコンテキストがあってUXとなります。次にコンテキストについてお話します。

コンテキスト

エンジニアであればよく議論の最中にもコンテキストという言葉を連呼されている方もいるのではないでしょうか。UX世界でのコンテキストとは「状況」「環境」「前後関係」のようなユーザーのタスクを達成する上で影響を与える事柄と捉えます。

smartphone4_woman.png

例えば、上記の画像からコンテキストを読み取ると、片手でデバイスを操作、一人、うつむきながら、といったことがコンテキストに当たります。移動していそうと言うような事実ではない想像の域を出ないものはコンテキストと考えないこととします。

普段開発しているときにはついつい画面の中だけで完結しがちですが、コンテキストを考慮するとより広いユーザー体験を設計できるようになります。

コンテキストの種類

コンテキストには7つの種類があるとCennydd Bowles氏の記事で語られています。

  1. バイス
  2. 環境
  3. 時間
  4. 行動
  5. パーソナル
  6. 場所
  7. ソーシャル

特に一番気になるのは5の個性についてです。

スマートフォンを常にユーザーと共に、あるいは少なくとも手の届くところに置いていると親しみを感じ、そこにパーソナルスペースを見出します。(中略) それらは、私たちが秘密を打ち明けられるパートナーであり、かつ超人的な力を与えてくれるツールなのです。(中略) このパーソナルスペースが侵害されると、人は強く反応します。煩わしいSMSのスパムを誰もが嫌がることや、掲示板での痛烈な議論もそうです。。ポータブルデバイスを利用したサービスを考えているならば、ユーザーがそれを使おうと選択した時点で、こうしたパーソナルな性質があることを考えるべきです。 コンテキストを理解する(切り口-5: パーソナル) | UX TIMES

パーソナルスペースは人が持つ安全圏のことであり、一般的に親しい人ほど近くにいても嫌悪感がないことが多いです。公共交通機関で間隔を開けたがる人が多いのはこの性質によるものと考えられます。 普段私達が使うデバイスもパーソナルスペースに侵入している以上ある程度心を許す存在です。PCよりもスマートフォンの方が常に持ち歩いている分、親しい感じがするのではないでしょうか。

当然親しい存在のスマートフォンで運用するサービスもユーザーのパーソナルスペースにいることに気を配るべきだと思います。必要のない通知を送っている場合などは気をつけたほうがいいかもしれません。

実は、コンテキストはここで紹介されている7つだけではないです。 常に想像を膨らませて各々のサービスでのコンテキストを考えてみてはいかがでしょう。

コンテキスト全部盛りでサービス作ればいいの?

コンテキストは必ずしも盛り込まないといけないものではないです。コンテキストがユーザーの行動に特に影響ない限りコンテキストの優先順位は高くありません。大切なのはサービス開発する上で、どのコンテキストを選択するのか (大切にするのか) ということです。 コンテキストが変わらないものでない以上、時代やシーンに合わせて考え直していくことが重要です。

まとめ

  • コンテキストとはユーザーのタスク達成に影響する様々なこと
  • コンテキストを考慮すると画面外のことにまで気に出来るようになる
  • コンテキストには7つの代表的な切り口が存在する
  • コンテキストの切り口はもっと存在する
  • どのコンテキストを選択するのかが大切

転じてElm meetup Tokyo #4に参加してElmerたちと触れ合った

突然ですが転職しました。8月からFringe81という会社で働いています。で、Elmを使い始めました。

fringeneer.hatenablog.com

最近ようやくElmの入り口に立ったばかりですが、Elm meetup Tokyo #4について記事にしてみます。
セッションは全部で3つとLTがいくつかありました。場所は弊社Fringe81だったので自分は運営に徹していましたが所々で参加できました。

複雑化するUIにどう立ち向かうか

セッション2つ目は@arowM_さんの発表でした。 まず前提としてElmと他のAltJS言語との大きな違いについて ElmはWebフロントエンドをかんたんにすることが目的 だと語られていました。 言語としての完成度 (あれこれ構文が揃っているとか) を第一で考えているのではなくあくまで必要なら構文は用意するというのが言語的特徴らしいです。 実際、Elmは学習コストが少なく目立ったハマリポイントもあまりないような気がしてます。 加えて、直感的に書いてしまったところではElmコンパイラに度々指示を受けることもしばしばですが、実行時エラーは驚くほど少ないです。 ここで語られてた背景を情報ソースとともに教えてほしかったのですが残念ながら機会がありませんでした。 後大切なこととして、手段にこだわらず、目的にあった最適な方法を取るべし とおっしゃっていました。 Elmでアーキテクチャを考える時には簡単な舞台が用意されています。その舞台でどういう方法を取るかは課題によって変えることができると感じているのでガッチリハマった気がします。

次に話題はCSS関連の話に移ります。CSS ModuleはElmでも人気のトピックらしく懇親会でもレスポンシブルへの対応をどうやっているのかと言う感じの知見を求める声を聞きました。論点はCSSとどう付き合っていくかということが多かった気がします。 elm-cssstyle-elementsの良し悪しははっきりと別れていないみたいです。どちらにしてもelm-cssは一つの解決策ということで紹介されていた印象です。

複雑化するUIにどう立ち向かうか - Elmの思想に学ぶ現代的なWebフロントエンド開発手法

スライドもElmで作られています!

github.com

実プロダクションで使った話

セッション3つ目は@jshosomichiさんの発表でした。 Elmはいい先生になるという言葉の通りElmを使うことでElmから学ぶことが多かったと語っておられました。 思考に注力させてくれる事が多くなって、Elmの型定義からPure JSで例外にするべきパターンが見えてくると言っていたのが刺さりました。 まさに今自分は学んでいる途中なのですが、新しい視点を得られて明日からのコーディングの楽しみが増えるセッションでした。

speakerdeck.com

懇親会

なんと懇親会の出席率は100%でした。 様子はと言うと皆さん自分が考えているプロダクトの話やアイディアについての話、知見を求める話やElmの将来の話など多岐にわたっていた印象です。 話す内容が一つのトピックに集中しない感じがまだまだ発展途上のElmらしさでしょうか。

終わってみての感想

Elmはまだまだ日本では利用者が少ない言語です。 懇親会でも関数型プログラミングの基礎についてといった初学者向けの話はあまりなく、すでにHaskellやElixirを習得している方も多くいらっしゃいました。 JSerの方がいきなり入りにくい土壌ではないのでどんどん入ってきてほしいと思います。一つのムーブメントが起きてElmがもっと日本中で使われるようになり議論が活発化してElmの未来を大いに語れる日が来ると良いなあ。 これからもElmと付き合うことになると思うのでまたアウトプットしていこうと思います。ではノシ

Domain Driven Design (DDD) Quickly を読み始めた

DDD Quickly

ひょんなことからDDDについて学ぶことになりまして、無料で配布されているDDD Quicklyの日本語訳バージョンを読み始めました。 あの分厚い書籍とは異なり80ページ強なので割りと読みやすそうです。

ドメイン駆動設計とは何か

ソフトウェアの設計手法は数あれどドメイン駆動設計 (DDD) は聞いたことはあったものの知りませんでした。
個人的に設計は苦手分野でありインプット状態はこんな感じだと思います。

本書ではまず、ソフトウェアは現実世界の問題を解決するものであり、そのためにはそのドメインに精通した知識が必要と言っています。 ドメインをうまく汲み取りソフトウェアに反映できれば最高そうですね。そのためにはドメインをうまく反映したモデルが必要です。

ドメイン -> 抽象 (モデル)

これによってエンジニアはドメインに関する知識がなくてもドメインモデルさえあれば優れたソフトウェアを作ることができます。
モデルはドメインの専門家の知識を取捨選択しながら作ることになります。
ドメインモデルはプロジェクトの関係者に向けて表現したもので、共通の言語でやり取りできるということですね。

ドメインの専門家から話を聞く機会が多々ありますが、こんなにうまく取捨選択できたことないので未だ半信半疑だったりします。
ウォーターフォールアジャイルなどのソフトウェア開発手法と比べられていましが、同じ土俵で比べるものなのか疑問でした。でも、設計過剰や実装過剰といった話は想像しやすかったですね。

ドメインモデル作成の流れはコミュニケーションの参考になりました。こんなにうまく必要なだけの情報を引き出せるようになりたいですね。

ここまで読んで

あまり特別なことはしてない印象を受けました。 要件を聞いて設計するのはいつもやっていることですし、そのために必要な情報を引き出すこともやっています。
でもですね、それをうまくプロジェクト関係者が共有できているのか、引っ張り出す必要のない知識まで設計に反映してないか。といった問題があることがわかりやすく書かれていたので、是非続きを読んでいこうと思います!

Jenkins 2.0おじさんとレガシー環境を駆け出していく

境遇

レガシー環境で働けているので改善活動が捗る今日この頃。 自動テストをいよいよ実行したいのでCIサポートしてくれるツールを考えてみた。 オンプレの制限があったのでJenkinsがまっさきに思い浮かんだがあまり導入にポジティブじゃなかった。 TECHNOLOGY RADARを読んであまりいい印象じゃなかったから。 とは言え課題のコード品質アップにCI環境は必須だ。 ちょうどpepaboが採用している記事を見かけたのでDrone.ioも検討したが、そのままでは利用できないのでplugin作ろうとしたが、あまりマイナーに突っ走ると周りがついてこれなくなると思ったので断念した。 そんなこんなで結局JenkinsおじさんとCIデビューすることになった。

インストー

サーバは適当に用意してOSにはCentOS7を採用した。 JenkinsはMasterとNode×2の構成にした。 Masterには実際のビルド実行環境を用意せず、Nodeにまかせることにした。 Masterの作成にはAnsible Playbookを作成した。 JenkinsのRoleは充実していてGalaxyで配布されているものをそのまま利用した。

github.com

NodeにはSSHとGitとJDK 1.8を最低限インストールしておき、Masterからの認証ができるようにしておく。 SSHを使ったNodeの追加はプラグインでサポートされているのでMasterにプラグインを導入するのを忘れずに。 JDK 1.8なのはJenkins 2.54からJava8が必須になったから。

jenkins.io

あとはNodeにビルド環境を構築すればおっけー。

Pileline as a code

CI環境を用意したらビルド時に行うこと、つまりPipelineを登録する。 Jenkins 2.0からはJenkinsfileをつかった登録方法が一般的だ。 Jenkinsfile用意してリポジトリ上で管理するとチームメンバに嫌でも目に入るし、何をやっているのかもなんとなくわかってもらえると思う。 Jenkinsfileの構文はとても簡単にできるDeclarative Pipelineとより高度な設定を記述できるScripted Pipelineが用意されている。

jenkins.io

やっていることはBuild, Test, Deploy, Lintで今のところはDeclarative Pipelineで運用している。 実行中に失敗したらRocketChatに通知するようにしている。 Declarative Pipelineでは失敗のステータスから成功に変わった時にイベントを登録する構文が用意されていないのでScripted Pipelineで解決できるなら試してみても良いかもしれない。

Jenkinsfileはリポジトリのルートにおいておくと便利だ。 Jenkinsのジョブ作成にGitHub Organization Folderを利用すると自動でJenkinsfileのあるリポジトリを見つけてJenkinsに登録してくれる。 リポジトリにWebHookを設定しておくとPush時に自動でジョブを実行してくれてCIを実現しやすくなる。 実はうまく行っていない事例としてブランチ名を xxx/yyy みたいにスラッシュを含むようにするとリポジトリスキャン時に登録できるもののPush時に自動でジョブを実行してくれないと言うものがある。

Blue Ocecan

Jenkinsの見た目にはMaterial Designを利用しているんだけど、近頃Blue Ocean 1.0がリリースされたこともあってインストールしている。

jenkins.io

見た目がモダンなデザインでGUIを利用してJenkinsfileを作成したりできるみたいだけど試せていない。 今のところは見た目のおしゃれさでしか恩恵を受けられていないんだけど、チームがJenkinsへ慣れ親しんでもらうためにGUIで色々できるようにしたい。

Jenkinsの所感

たまーにリポジトリのfetchに失敗していたりして、 git fsckgit gc をやってやらないといけない時があるのが謎だったりするのですが、今のところ問題なくコード品質の改善に一役買ってくれている。 Jenkinsはジョブの登録が自由にできるため、CIに関係ないジョブ (ansible playbook使ったサーバ構成のジョブなど) を登録できてしまうが、CIと関係のないジョブは登録しないようにしようと思った。 というのもジョブの実行にWeb APIが使えて便利だからといろいろ登録すると、何のためにとか依存関係が不透明化したジョブがたくさんできて煩雑になりそうだからだ。 Jenkinsおじさんになんでも押し付けるのではなく解決したい課題にあったツールを模索していきたいところである。