Wondershake 開発者ブログ

LOCARI(ロカリ)の運営会社の開発者ブログです。

RailsアプリケーションのためのフロントエンドLint環境の整備(SCSS編)

こんにちは。フロントエンドエンジニアの佐々木です。

前回は JS 周りの Lint 環境を整備しました。

engineering.wondershake.com

今回はスタイルシート周りの Lint 環境を整備した話になります。

はじめに

スタイルシートはある程度のルールをチーム内で共有しておかないとすぐに雑多なコードになってしまいます。

コーディングルールを明文化してレビューで指摘するといった方法もありますが、できれば Lint ツールでコーディングスタイルを定義して、ルールに逸脱したコードは CI で落とすようにすると健全です。

今回は 2 年間運用されてきた scss ファイルに scss-lint を実行して、エラーを 0 にして CI で弾くことができる状態にするのがゴールになります。

なお scss-lint を導入するにあたりこちらの記事を参考にさせて頂きました。

自動検出と自動修正でCSSを保守する - Qiita

scss-lint

f:id:sskyu:20160725165603p:plain

インストール

scss-lint の Installation を見ると、

$ gem install scss_lint

でインストールするか、Gemfile に

gem 'scss_lint', require: false

を追記して bundle install することでインストールできます。

依存する gem は Gemfile に書いておきたいので、後者でインストールしました。

scss-lint を実行してみる

実行するには下記のコマンドを実行します。

$ bundle exec scss-lint app/assets/stylesheets/**/*.scss
app/assets/stylesheets/admin.scss:1:1 [W] Comment: Use `//` comments everywhere
app/assets/stylesheets/admin.scss:10:9 [W] StringQuotes: Prefer single quoted strings
app/assets/stylesheets/admin.scss:11:9 [W] StringQuotes: Prefer single quoted strings
# 以下膨大なエラーメッセージが続く

エラーが多すぎて原因がよくわからないので、オプションに --format=Status を付けてもう一度実行します。

$ bundle exec scss-lint --format=Stats app/assets/stylesheets/**/*.scss
 257  PropertySortOrder              (across 58 files)
 217  SpaceBeforeBrace               (across 58 files)
 175  NameFormat                     (across 46 files)
 154  StringQuotes                   (across 67 files)
 102  SelectorDepth                  (across 23 files)
  93  NestingDepth                   (across 28 files)
  75  QualifyingElement              (across 31 files)
  65  ColorVariable                  (across 31 files)
  59  EmptyLineBetweenBlocks         (across 21 files)
  47  SpaceAfterPropertyColon        (across 16 files)
  39  DeclarationOrder               (across 17 files)
  35  Shorthand                      (across 23 files)
  35  IdSelector                     (across 17 files)
  34  ColorKeyword                   (across 22 files)
  32  LeadingZero                    (across 11 files)
  30  PseudoElement                  (across  7 files)
  27  FinalNewline                   (across 27 files)
  26  SpaceAfterComma                (across  2 files)
  21  ImportantRule                  (across 11 files)
  19  Indentation                    (across  7 files)
  16  SelectorFormat                 (across  9 files)
  16  BorderZero                     (across 10 files)
  14  VendorPrefix                   (across  3 files)
  14  SingleLinePerSelector          (across  9 files)
  12  Comment                        (across  7 files)
  12  ZeroUnit                       (across  7 files)
   5  EmptyRule                      (across  4 files)
   4  UnnecessaryMantissa            (across  1 files)
   4  MergeableSelector              (across  3 files)
   3  TrailingSemicolon              (across  3 files)
   3  PlaceholderInExtend            (across  1 files)
   1  SpaceBetweenParens             (across  1 files)
   1  SpaceAfterVariableName         (across  1 files)
----  -----------------------        -----------------
1647  total                          (across 91 files)

今のところ 91 ファイルに渡って 1647 個のエラーがあることが分かります。

デフォルトでどんなルールが設定されているかというのはこちらのドキュメントにまとまっています。

scss-lint/README.md at master · brigade/scss-lint · GitHub

ここからエラーを 0 にする修正を行っていきます。

scss-lint.yml を作成する

デフォルトの設定だとかなり厳しいルールになっているので、幾つかのルールを無効化するように設定することにします。

プロジェクトルートに .scss-lint.yml を作成し、ドキュメントを見ながらルールを無効化していきます。

この段階ではこのような .scss-lint.yml が出来上がりました。

scss_files: 'app/assets/stylesheets/**/*.scss'

exclude: 'app/assets/stylesheets/lib/**'

linters:
  BorderZero:
    convention: none

  ColorKeyword:     # ルール名に対して
    enabled: false  # enabled: false を指定して無効化する

  ColorVariable:
    enabled: false

  Comment:
    enabled: false

  DeclarationOrder:
    enabled: false

  EmptyLineBetweenBlocks:
    enabled: false

  IdSelector:
    enabled: false

  LeadingZero:
    enabled: false

  NameFormat:
    convention: '^[a-z][-a-z0-9_]*$'

  NestingDepth:
    max_depth: 4

  PropertySortOrder:
    enabled: false

  QualifyingElement:
    allow_element_with_attribute: true
    allow_element_with_class: true

  SelectorDepth:
    max_depth: 4

  Shorthand:
    enabled: false

  SpaceBeforeBrace:
    allow_single_line_padding: true

  StringQuotes:
    style: double_quotes

設定ファイルを作った状態で scss-lint を実行してみます。

$ bundle exec scss-lint --format=Stats
217  SpaceBeforeBrace               (across 58 files)
 62  StringQuotes                   (across 12 files)
 47  SpaceAfterPropertyColon        (across 16 files)
 32  NestingDepth                   (across 13 files)
 30  SelectorDepth                  (across 10 files)
 30  PseudoElement                  (across  7 files)
 27  FinalNewline                   (across 27 files)
 26  SpaceAfterComma                (across  2 files)
 21  ImportantRule                  (across 11 files)
 19  Indentation                    (across  7 files)
 16  SelectorFormat                 (across  9 files)
 14  SingleLinePerSelector          (across  9 files)
 14  VendorPrefix                   (across  3 files)
 12  ZeroUnit                       (across  7 files)
  5  EmptyRule                      (across  4 files)
  4  UnnecessaryMantissa            (across  1 files)
  4  MergeableSelector              (across  3 files)
  3  NameFormat                     (across  1 files)
  3  TrailingSemicolon              (across  3 files)
  3  PlaceholderInExtend            (across  1 files)
  2  BorderZero                     (across  2 files)
  1  SpaceAfterVariableName         (across  1 files)
  1  SpaceBetweenParens             (across  1 files)
---  -----------------------        -----------------
593  total                          (across 77 files)

ソースを変更せず、ルールを緩くしたことで 593 まで減りました。

csscomb で自動フォーマットする

f:id:sskyu:20160725174344p:plain

上記出力結果の 217 SpaceBeforeBrace (across 58 files)154 StringQuotes (across 67 files) などは簡単な修正で直りそうです。
人間の手でやると間違ってしまう可能性があるので、ツールを使ってやると良いでしょう。

csscomb を使うとスタイルシートの自動フォーマットをすることができます。

CSScomb: Makes your code beautiful

インストール

csscombは npm で提供されているので、node.js がインストールされている必要があります。

開発時に使うモジュールなので -D (--save-dev) を付けてインストールします。

$ npm i -D csscomb

csscomb用の設定ファイルの作成

csscomb を使い、どのようにフォーマットするのか指定するための設定ファイルを作ります。

下記のページからどのようにフォーマットしたいか、選択形式で作ることができます。

CSScomb: Build config

f:id:sskyu:20160725175703p:plain

例えば一番初めの選択肢の場合、Remove empty rulesets を有効にするかどうかを聞かれています。
有効にする場合は左のコードをクリックして、無効にする場合は右のコードをクリックします。
これを 24 つ続けていくと .csscomb.json のサンプルコードが表示されるので、このコードをコピーしてプロジェクトルートに .csscomb.json を作成しましょう。

作成したら下記コマンドで実行します。
(※ 大量の scss ファイルの差分が発生するのでここまでの作業をコミットしておくと良いです。)

$ ./node_module/.bin/csscomb app/assets/stylesheets/**/*.scss

コマンドが長いので package.json の scripts に登録しておきます。

  scripts: {
     "format:scss": "csscomb app/assets/stylesheets/**/*.scss"
  }

これで $ npm run format:scss で csscomb が動くようになりました。

いくつかの調整をして、.csscomb.json はこのような形になりました。

{
  "exclude": [
    "node_modules/**",
    "app/assets/stylesheets/lib/**",
    "app/assets/stylesheets/shared/colors.scss",
    "app/assets/stylesheets/web/mixins/z_indexes.scss"
  ],
  "always-semicolon": true,
  "color-case": "lower",
  "block-indent": "  ",
  "color-shorthand": true,
  "element-case": "lower",
  "eof-newline": true,
  "leading-zero": true,
  "quotes": "double",
  "space-before-colon": "",
  "space-after-colon": " ",
  "space-before-combinator": " ",
  "space-after-combinator": " ",
  "space-between-declarations": "\n",
  "space-before-opening-brace": " ",
  "space-after-opening-brace": "\n",
  "space-after-selector-delimiter": "\n",
  "space-before-selector-delimiter": "",
  "space-before-closing-brace": "\n",
  "strip-spaces": true,
  "tab-size": true,
  "unitless-zero": true
}

csscomb によるフォーマットを適用した状態で scss-lint を実行してみます。

$ bundle exec scss-lint --format=Stats
 32  NestingDepth                  (across 13 files)
 30  SelectorDepth                 (across 10 files)
 30  PseudoElement                 (across  7 files)
 26  SpaceAfterComma               (across  2 files)
 21  ImportantRule                 (across 11 files)
 16  SelectorFormat                (across  9 files)
 14  VendorPrefix                  (across  3 files)
  5  EmptyRule                     (across  4 files)
  4  MergeableSelector             (across  3 files)
  4  UnnecessaryMantissa           (across  1 files)
  3  PlaceholderInExtend           (across  1 files)
  3  NameFormat                    (across  1 files)
  2  BorderZero                    (across  2 files)
  1  SpaceAfterVariableName        (across  1 files)
  1  SpaceBetweenParens            (across  1 files)
---  ----------------------        -----------------
192  total                         (across 35 files)

フォーマットをして 192 個までエラーが減りました。

エラーを 0 にするまでの作業

ここからは地道に修正していくしかありません。

特別にやったことといえば、 VendorPrefix のルールに適合するため、autoprefixer-rails をいれて、ベンダープレフィックスは自動生成されるようにしたことくらいでしょうか。

GitHub - ai/autoprefixer-rails: Autoprefixer for Ruby and Ruby on Rails

特に難しかったのが NestingDepthSelectorDepth のルールをパスすることでした。

初期のソースは何故かネストが深い箇所が多々あり、まずはネストを一段下げてスタイル崩れが起きないか確認を頻繁に行いました。
ネストが深いとスタイルを上書きしたい場合にやりづらかったり、出力される css ファイルのサイズが肥大化したりと良いことがありません。
詳細度を上げてスタイルを作るよりも、クラス名にユニーク性を持たせてネストを下げるようにするべきです。

結局、設定で depth: 4 を指定しましたが、止むを得ず depth: 5 を指定して修正することになりました。

色々な妥協を経て、.scss-lint.yml はこのようになりました。

scss_files: 'app/assets/stylesheets/**/*.scss'

exclude: 'app/assets/stylesheets/lib/**'

linters:
  # border: 0 -> border: none
  BorderZero:
    convention: none

  # color: white などの利用を禁止
  ColorKeyword:
    enabled: false

  # color: $body-color のように変数を強制する
  ColorVariable:
    enabled: false

  # /* */ でのコメントを禁止
  Comment:
    enabled: false

  # @extend, @include, @content の記述順をスコープの先頭に強制する
  DeclarationOrder:
    enabled: false

  # ルール間に改行を強制する
  EmptyLineBetweenBlocks:
    enabled: false

  # 空のルールを禁止する
  EmptyRule:
    enabled: false

  # IDセレクタに対するスタイル付けを禁止する
  IdSelector:
    enabled: false

  # !importantを禁止する
  ImportantRule:
    enabled: false

  # opacity: 0.5 -> opacity: .5
  LeadingZero:
    enabled: false

  # 変数名の命名規則を定義する
  NameFormat:
    convention: '^[a-z][-a-z0-9_]*$'

  # ネストの深さを制限する (default: 3)
  NestingDepth:
    max_depth: 5

  # プロパティの記述順を強制する
  PropertySortOrder:
    enabled: false

  # div.selector などの書き方を制限する
  QualifyingElement:
    allow_element_with_attribute: true
    allow_element_with_class: true

  # セレクタの深さを制限する (default: 3)
  SelectorDepth:
    max_depth: 5

  # セレクタの名前のルールを定義する (default: hyphenated_lowercase)
  SelectorFormat:
    ignored_names:
      - 'fb_iframe_widget'  # facebook widget
      - 'field_with_errors' # generated by rails
    ignored_types:
      - 'id'

  # color: #ff0000 -> color: #f00 のように短い記述を強制する
  Shorthand:
    enabled: false

  SpaceAfterComma:
    exclude:
      - 'app/assets/stylesheets/shared/colors.scss'

  # p{} -> p {} のようにスペースを強制する
  SpaceBeforeBrace:
    allow_single_line_padding: true

  SpaceBetweenParens:
    exclude:
      - 'app/assets/stylesheets/shared/colors.scss'

  # 文字列を表すクォートの種類を指定する (default: single_quotes)
  StringQuotes:
    style: double_quotes

  # 必要のないvendor-prefixの記述を禁止する
  VendorPrefix:
    enabled: false

この状態で scss-lint を実行します。

$ bundle exec scss-lint --format=Stats
$

何も出力が表示されないということで、エラーを 0 にすることができました。

scss-lint を npm scripts に登録する

CI で実行するときに、npm test を叩けばフロントエンドのテストが走る状態にしたいので、bundle exec scss-lint も npm scripts に登録しておきます。

前回の内容も含めると、下記のようになります。

  "scripts": {
    "format:scss": "csscomb app/assets/stylesheets/**/*.scss",
    "test": "npm run test:lint",
    "test:lint": "npm run test:eslint && npm run test:coffeelint && npm run test:scsslint",
    "test:eslint": "eslint --ext .js,.jsx app/assets/javascripts",
    "test:coffeelint": "coffeelint -f .coffeelint.json app/assets/javascripts",
    "test:scsslint": "bundle exec scss-lint"
  },

これで最低限の品質を担保する仕組みが整いました。

おまけ

エディタに Lint 環境を整備する

私は ATOM Editor を使っているのですが、エディタが各種 Linter に対応しているとリアルタイムにエラーを報告してくれて便利なので是非設定しましょう。

ATOM に linter-scss-lint をセットアップする

まず linterlinter-scss-lint というパッケージをインストールします。

インストールしたら linter-scss-lint の Setting を開きます。

(ここからは ruby のバージョン管理に RVM を利用している場合の紹介になります。)

公式ドキュメントにある通り、まず scss-lint コマンドが使えるようになっている必要があります。

$ gem install scss_lint

次にコマンドのパスを取得します。

$ which scss-lint
/Users/sasaki_yutaka/.rvm/gems/ruby-2.3.1/bin/scss-lint

最後に linter-scss-lint の Settings の Executable Path に、上記パスの binwrappers に置換したパスを入力します。

/Users/sasaki_yutaka/.rvm/gems/ruby-2.3.1/wrappers/scss-lint

f:id:sskyu:20160726161007p:plain

この状態で .scss ファイルを編集するとリアルタイムに lint 結果が表示されて作業が捗ります。

f:id:sskyu:20160726161520p:plain

同様に、linter-coffeelintlinter-eslint も設定しておくと良いでしょう。

まとめ

この記事では Rails アプリケーションに scss-lint を設定する一例をご紹介しました。

エラーを 0 にして CI で回せることをゴールに設定したため妥協してルールを緩くしました。
ここからは緩くしたルールを厳しくして、リファクタに努めていくフェーズとなります。

Wondershake ではサーバーサイドエンジニアや iOSAndroid ディベロッパーを募集しています。
興味をお持ちの方は是非こちらからご応募下さい!

www.wantedly.com

Locariで使用しているCocoaPodsたち

こんにちは、iOS開発担当の藤井です。

Cocoapods使ってますか? 今回はみんな大好きCocoapodsの中でLocariでも使用しているライブラリを幾つか紹介します。

AFNetworking

https://github.com/AFNetworking/AFNetworking

ネットワーク系のライブラリとしては知らない人はいないくらい有名なライブラリで、LocariでもAPIとの接続で利用しています。 単純にURLリクエストを投げることから複雑なことまで何でもできるので、サーバサイドと繋げるアプリの場合は最初の選択肢に上がってくるのはこのライブラリではないかと思います。

APParallaxHeader

https://github.com/apping/APParallaxHeader パララックス効果のついたテーブルヘッダを作るのに便利なのがこのAPParallaxHeaderです。 Locariでは各記事のカバー写真をパララックス効果を入れて表示しているのでこのライブラリを使用しています。

Fabric & Crashlytics

Twitterに買収されたCrashlyticsがFabricとして提供しているのがこれで、バグレポートやアプリの利用状況などをリアルタイムで送ってくれます。

ionicons

http://ionicons.com/ ioniconsはiOS/Androidアプリ内でよく使いそうなアイコンをカスタムフォントとして利用できるライブラリです。UILabel内で文字としても表示できますし、UIImageView内に画像として表示することもできるので非常に使い勝手が良いです。

MagicalRecord

https://github.com/magicalpanda/MagicalRecord これもかなり有名なライブラリですが、CoreDataを扱う上で非常に便利なO/Rマッパーです。これを利用せずにCoreDataを扱うことはもうあまりないのでは…? というくらい依存度が高いです。

SDWebImage

https://github.com/rs/SDWebImage AFNetwowrkingと同じく画像ダウンロードを行うライブラリとしては有名なこのライブラリも入れています。 Jpeg形式の画像だけでなくWebPも扱えるため、転送量のダイエットにも役立ちます。

youtube-ios-player-helper

https://github.com/youtube/youtube-ios-player-helper 記事内でYouTube動画を再生する為に利用しています。VideoIDをセットするだけでYouTube再生用のViewを生成してくれます。フルスクリーンにならずに再生できるのがさり気なく良いです。

VTAcknowledgementsViewController

https://github.com/vtourraine/VTAcknowledgementsViewController どういったCocoapodsを利用しているのかライセンスとしてまとめてくれる便利なライブラリがこれです。plistで設定してあげると自動的に有効なCocoapodsがリストアップされたViewControllerを生成してライセンスとして表示できます。 アプリに使用しているライブラリのライセンス表示を入れたいけどライブラリを追加・削除する度に更新するのが面倒、という時に活躍してくれます。

Locariでは他にもたくさんのCocoaPodsを利用していますが、主だったものを上げてみました。 他にもこんな便利なCocoaPodsあるよ!というのがあればぜひ教えてください :)

またWondershakeではCocoaPodsに詳しいiOSエンジニアも募集中です。 www.wantedly.com

RailsアプリケーションのためのフロントエンドLint環境の整備(JS編)

こんにちは。フロントエンドエンジニアの佐々木です。

まだ入社して2ヶ月ほどですが、最初の1ヶ月は主に Lint 環境の整備などを行ってきたのでその紹介をしてみます。

はじめに

Wondershake では Locari というサービスを運営しているのですが、2年以上運用されており様々な人達が関わって作られてきました。

コードベースは Ruby on Rails 4 の上に作られており、github-flow 形式で PR を作って開発しています。

レビューで致命的な問題は取り除かれているものの、多人数で作り上げられたコードは人によって書き方が違ったりと、どうしても細部のルールが揺れてしまっている状態でした。

そこでコーディング規約を機械的にチェックする Linter を入れ、ルールに適合しないコードは CI(CircleCI) で落とすようにしました。
これで最低限コーディング規約に従ったコードだけをレビューに出すことができ、一定の品質を保てるようになります。

この記事でやること

  • CoffeeLintの導入
  • ESLintの導入

※ nodejs が既に入っていることを前提として書いています。

CoffeeLintの導入

f:id:sskyu:20160627204133p:plain

CoffeeLint は特に悩むこと無く導入することが出来ると思います。

$ npm i -D coffeelint  # devDependenciesに追加

CoffeeLint は CLI で実行できるようなので、試しに動かしてみます。

./node_modules/.bin/coffeelint app/assets/javascripts
  ✗ app/assets/javascripts/admin_post_form/form/dropdown.coffee
     ✗ #7: Line exceeds maximum allowed length. Length is 82, max is 80.
  ✓ app/assets/javascripts/admin_post_form/form/edit_post_cover_picture.coffee

  # 途中省略

✗ Lint! » 33 errors and 0 warnings in 52 files

ほとんどのエラーが mux_line_length のエラーでした。

※ 各ルールは公式ドキュメントでご確認ください。

CoffeeLint - Lint your CoffeeScript

デフォルトで有効なルールには適合するようにコードを修正したいところですが、CoffeeScript で一行 80 文字制限は少々厳しいので、このルールを無視して実行するようにしたいと思います。

プロジェクトルートに下記の内容で .coffeelint.json を作成します。
(ファイル名はなんでも良いですが、.json で作成するのをおすすめします。)

{
  "max_line_length" : {
    "value": 80,
    "level": "ignore"
  }
}

"level": "ignore" にすることでルールを無視することができます。
"level": "warn" だと警告を出力し、 "level": "error" だとエラーを出力します。

もし一行の文字数の上限を上げたい場合は "value": 120, "level": "error" などにすると良いです。

この設定ファイルを読み込むように指定し、もう一度 CoffeeLint を実行してみます。

./node_modules/.bin/coffeelint -f .coffeelint.json app/assets/javascripts
  ✓ app/assets/javascripts/admin_post_form/form/dropdown.coffee

  # 途中省略

 Ok! » 0 errors and 0 warnings in 52 files

エラーが表示されなくなりました。

最後に、CoffeeLint を動かすコマンドが長いので npm scripts に登録しておきます。

package.json の scripts の項に test:coffeelint として登録します。

"scripts": {
  "test:coffeelint": "coffeelint -f .coffeelint.json app/assets/javascripts"
}

これで $ npm run test:coffeelint だけで CoffeeLint を走らせることが出来るようになりました。

ESLintの導入

f:id:sskyu:20160627203914p:plain

管理画面側の一部で browserify-rails + babelify を使って書かれた ES2015+ なコードがあります。
これらを ESLint を使って構文チェックさせたいと思います。

ESLint には他の人が作った設定をインポートできる仕組みがあるので、定番の airbnb の設定を使います。

eslint-config-airbnb

下記コマンドで一連のモジュールをインストールします。

npm i -D eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y eslint babel-eslint

それぞれのモジュールの役割は以下の通りです。

  • eslint-config-airbnb
    • airbnb の eslint 設定
    • 今回はこれを使いまわしたい
  • eslint-plugin-import
    • eslint の設定をインポートするために必要
  • eslint-plugin-react
    • eslint で react を扱うために必要
  • eslint-plugin-jsx-a11y
    • eslint で jsx を扱うために必要
  • eslint
    • eslint 本体
  • babel-eslint
    • babel 側で読み込んだ拡張構文を eslint 側に対応させるために必要
    • ES6/ES7 の範囲で書かれていれば不要
      • React Components で static property を使っているため今回は必要

モジュールをインストールできたらプロジェクトルートに .eslintrc を作成します。

{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "env": {
    "browser": true,
    "node": true
  },
  "globals": {
    "ga": true,
    "$": true,
    "I18n": true
  },
  "rules": {
    // 必要ならここに airbnb の eslint ルールを上書きしていく
  }
}

"extends": "airbnb"eslint-config-airbnb の設定を読み込んでいます。
"globals" には window 直下に作られているグローバル変数を記述しておきます。
airbnb のルールは結構厳しめで作られているので、不都合ある場合は "rules" に追記してルールを上書きしていきます。

この時点で一度 ESLint を実行してみます。

./node_modules/.bin/eslint --ext .js,.jsx app/assets/javascripts

  # 中略

  5:398  error    Missing semicolon                                                                                              semi
  5:401  error    'jQuery' is not defined                                                                                        no-undef

✖ 53669 problems (52666 errors, 1003 warnings)

出力が完了するまで20秒くらいかかりました。
これだけ時間がかかった原因は app/assets/javascripts/lib/* 以下の js ファイルも ESLint 対象になっているためのようです。

外部ライブラリは ESLint 対象にする必要がありませんので、プロジェクトルートに下記の内容で .eslintignore を作成し、ESlint の対象にしたくないファイルを追記していきます。

app/assets/javascripts/lib/*

もう一度実行してみます。

./node_modules/.bin/eslint --ext .js,.jsx app/assets/javascripts

/path/to/hoge.js
  6:3  warning  Unexpected constant condition  no-constant-condition

/path/to/fuga.js
   8:3  warning  Unexpected constant condition  no-constant-condition
  26:3  warning  Unexpected constant condition  no-constant-condition

/path/to/piyo.js
  6:3  warning  Unexpected constant condition  no-constant-condition

✖ 4 problems (0 errors, 4 warnings)

出力が減って見やすくなりました。

no-constant-condition の警告が出力されています。
当該箇所のコードは while (true) {} で無限ループを作っている箇所でした。
これはどうしてもコード側を修正することができないので、警告を表示しないようにルールを追加します。

{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "env": {
    "browser": true,
    "node": true
  },
  "globals": {
    "ga": true,
    "$": true,
    "I18n": true
  },
  "rules": {
    // 追記
    "no-constant-condition": ["error", { "checkLoops": false }]
  }
}

もう一度実行してみます。

./node_modules/.bin/eslint --ext .js,.jsx app/assets/javascripts

出力無しなので正常終了したようです。

こちらもコマンドが長いので package.json の scripts に登録しておきます。

"scripts": {
  "test": "npm run test:lint",
  "test:lint": "npm run test:coffeelint && npm run test:eslint",
  "test:coffeelint": "coffeelint -f .coffeelint.json app/assets/javascripts",
  "test:eslint": "eslint --ext .js,.jsx app/assets/javascripts"
}

これで $ npm t を実行するだけで npm run test:coffeelintnpm run test:eslint が実行されるようになりました。
(あとで "test:unit" を作ろうと思っているので "test""test:lint" を分けています)

まとめ

今回は CoffeeLint と ESLint の導入方法をご紹介しました。
ブランチをプッシュしたら CI で npm t を実行するようにすればコーディング規約を逸脱したコードがマージされることを防ぐことができます。

今回は長くなったので JS 編としました。
似たような感じで scss-lint の導入についても後日ご紹介できればと思います。

Wondershake では サーバーサイドエンジニアや iOSAndroid ディベロッパーを募集しています。
興味が湧いた方は是非こちらからご応募下さい!

www.wantedly.com

Androidアプリのテスト自動化

WondershakeでLocariのAndroidアプリを開発している代蔵です.

今回は,私が担当しているAndroidアプリのCIについてご紹介しようと思います. Androidアプリの開発者が私一人ということもあり,テストなどを出来るだけ自動化することで開発に専念できるような環境を作りました.

大まかな流れ

  1. GithubへPush (marge)
  2. PushをトリガーにCircleCIでUnitテスト,UIテスト
  3. Slackに結果を通知
  4. Fabricで開発チーム(release)・社内全体(master)にAPKを配布
  5. AWS Device Farmでモンキーテスト(master)

f:id:takumiumiu:20160623175741p:plain

1 GithubへPush(marge)

書いたコードをGitHubへPush.または,release, masterブランチへのPRのmerge.

2 PushをトリガーにCircleCIでUnitテスト,UIテスト

以下はcircle.ymlの一部抜粋になりますが,CircleCI上でUnitテストとUIテストを行っています. UnitテストはJUnit,UIテストはSpoonを使っています. Spoonを使っている主な理由は,画面のスクリーンショットを記録することでUIに問題ないかどうかを目視で確認できるようにするためです.

test:
  pre:
    # create sdcard
     - mksdcard -l e 512M mysdcard.img

    # start emulator
     - emulator -avd circleci-android22 -no-audio -no-window -sdcard mysdcard.img:
         background: true
         parallel: true

    # wait for booted
     - circle-android wait-for-boot

    # avoid com.android.builder.testing.api.DeviceException: com.android.ddmlib.ShellCommandUnresponsiveException
     - sleep 10

  override:
     - ./gradlew testDebug

     - ./gradlew spoonDebugAndroidTest:
         timeout: 1800

     - cp -r /home/ubuntu/locari_android/app/build/outputs/apk/* $CIRCLE_ARTIFACTS
     - cp -r /home/ubuntu/locari_android/app/build/spoon-output/* $CIRCLE_ARTIFACTS

3 Slackに結果を通知

CircleCIの結果をSlackに通知しています.

4 Fabricで開発チーム(release)・社内全体(master)に配布

releaseブランチでテストが全て通った場合は開発チームに,masterブランチでテストが全て通った場合は社内全体へFabric経由でAPKが配布されます. その他のブランチでは,配布されないようになっています.

5 AWS Device Farmでモンキーテスト(master)

最後に,masterブランチですべてのテストが通った場合にのみ,AWS Device Farmでテストを行うようになっています. これにより,Android版Locariでは最低限の品質を担保しています.

アプリのリリース

CircleCI上のUIテストで取得したスクリーンショットAWS Device Farmで問題が無いかどうかを確認. 全てに問題がなかった場合にのみCircleCIからAPKをダウンロードし,Google Playでリリースするという流れになっています. f:id:takumiumiu:20160623180604p:plain

最後に

細かい部分などは省略していますが,弊社のリリースまでの全体像を把握することが出来るかと思います. アプリのリリース作業などはまだ自動化していませんが,今のところ自動化の必要性は感じていません.

最近CircleCI上でOutOfMemoryErrorが多発していて,複数端末でのUIテストが出来ていないので,CircleCIから別のものに移行しようと検討中です.

弊社ではAndroidiOS,サーバサイドエンジニアを募集しています.ご興味の有る方がいれば是非こちらからご応募下さい!

www.wantedly.com

WWDC16 キーノート発表内容のまとめ

こんにちは、iOSエンジニアの藤井です。

現地時間13日の朝に行われたばかりのWWDC 16のキーノートで発表された内容のうち、注目すべき点を速報っぽく簡単にまとめました。

watchOS

watchOSは3が発表されました。

  • アプリの起動速度が爆速の7倍に。メモリとバックグラウンド更新を活用して実現。
  • アプリにすぐにアクセスできるドック機能も追加。
  • 手書き入力の強化。アルファベットだけでなく漢字(中国語)にも対応してます。
  • SOS発信機能。長押しすることで自動的に警察や登録しておいた家族などに自身の位置などを送信。
  • その他エクササイズ系の機能充実。

と3になってやっとそれらしい機能が充実してきたかなという印象。

tvOS

Apple TVに入っているtvOSは主にSiriの進化。文章での音声検索が相当優秀になったとのこと。 他には、シングルサインオン機能で、iOS側でログインしていればApple TVでも自動でログインされ、ID/Passwordの入力が不要になります。

macOS

OS Xは噂通りmac OSへ改名。ここ数年のカルフォルニアに関する固有名詞を付けるルールは引き継がれ、Sierraとなりました。 元ネタはシエラネバダ山脈のことかな。

主な追加機能としては

  • Siri Mac版がついに登場。ファイル検索をしてくれるなどよりアシスタントっぽくなりました。
  • オートアンロック機能。Apple Watchで画面ロックを解除できるようになりました。
  • クリップボード共有機能。Macでコピーした文章などをiPhoneで貼り付けできるように。またその逆もしかり。
  • iCloud経由でファイル間共有の強化。
  • Macのハードディスクが一杯になった時に不要なファイルを自動で整理して空き容量を増やす便利機能もしれっと追加。
  • Apple Payでの支払い。ブラウザ上でApple Payボタンを利用する際に手元のiPhoneで認証すればそのまま支払いできる。

辺り。

iOS

iOSはついに10へ! ということで10の機能更新が行われました。

  1. Notificationやコントロール機能の充実。3D Touchで更に情報を引き出せるようになったり、返信周りがかなり拡充されたのでもはやアプリを開かなくても用が済むように。

  2. Siriのサードパーティへの開放。Siriがサードパーティのアプリと一部連携できるようになり、WeChatのメッセージに返信したり、Uberを呼んだりできるようになりました。

  3. キーボードがよりインテリジェンスに。Deep Learningによる予測変換で会話の流れを読んで変換候補を出してくるようになりました。単純にテキストの候補だけでなく現在位置や電話帳、スケジュールなどを候補に出したり登録したりといったことができるようになります。ちゃんとワークすればかなり便利そうな予感。

  4. 写真アプリも更新。顔や場所などを認識して自動でカテゴリ分け、ロードムービーまで生成してくれるようになりました。Googleフォトやショートムービー作成系アプリを一気に倒しにきてる感。

  5. 地図アプリもスケジュールなどから行く場所を予測して表示などができるように。ナビもちょっと優秀になりました。

  6. ミュージックアプリはデザインの一新。一応使いやすくなったとのこと。

  7. Newsアプリも同じくデザインの一新。ミュージックアプリ同様Boldのフォントを使ったデザインを押し出していく様子。

  8. 日本だといまいち馴染みの薄いHome KitはHomeアプリとして登場。手元のアプリひとつで家のもの全てが操作できるように(したい)。

  9. 電話も更新。留守番電話のメッセージの書き起こし(beta)やサードパーティーで提供されている電話機能と繋げて電話できるように。

  10. メッセージアプリは大幅アップデートで、サードパーティーで提供されているようなおもしろ機能を一通り追加。そしてメッセージストアを用意して開発者へ開放、スタンプなど拡張機能を自由に追加できるように。

Swift

Swift関連ではSwift playgroundsというiPadアプリが登場。小学生くらいの子どもたちが楽しくSwiftやプログラミングを学べる仕組みが用意されているようです。子ども向けながら、大人の初学者でも利用できそうなくらいの作り込み。

まとめると

watchOS, tvOS, macOS, iOSという4種類のOSとそれが入ったデバイス間でのスムーズな連携の強化が今回のテーマの一つのように感じられました。前バージョン辺りからその傾向はありましたが、今回もオートアンロック機能やクリップボード共有、Apple Payでの支払いなど、今まで欲しかった連携機能が欲しい形で追加されています。どれか一つのOSやデバイスに集約するのではなく、それぞれに役割を与えて連携させていくという考えた方は個人的に好感が持てます。 他では、Siriとそれに付随するDeep Learning機能の追加、今まで開発者へ開放されていなかったAPIの部分開放辺りが注目すべき点でしょうか。

4つの新しいOSの開発者プレビュー版は本日から利用開始とのことで、早速触ってみようと思います!

SQLだけで勤怠データから曜日ごと、日中・深夜勤務時間を算出する(Postgres編)

こんにちは。千葉です。

開発者ブログをはじめよう、ということで初ポストです。

弊社には勤怠管理用の社内システムがあり(ちょうどいい勤怠システムってあんまりないですよね...!)そのメンテナンスもしています。

そこで今回「曜日や日中・深夜の勤務時間によって時間数を分けて表示してほしい」という案件が降ってきました。 素直にRuby on Railsで実装したり、適切な設計がしてあれば簡単な実装ですが、諸事情によりSQL(Postgres)だけで表示したい!ということになりました...!厄介ですね。

どうしよう、どうしようと先延ばしにしているうちに同じく開発部のエリックが考えてくれました。 方針としてはこうです。

  • 曜日は EXTRACT(DOW FROM date) すれば簡単に出せるのでそれで判定
  • 時間は LEASTGREATEST を駆使して境目の時間ごとに判定

Locariでは普段MySQLを使っているので、Postgresの勉強になりました。

この方針にのっとって、下記のテーブルがあるとすると、 (実際のテーブルのカラムをいくつか省いています)

                                            Table "public.user_attendances"
          Column          |            Type             |                           Modifiers
--------------------------+-----------------------------+---------------------------------------------------------------
 id                       | integer                     | not null default nextval('user_attendances_id_seq'::regclass)
 status                   | integer                     | not null default 0
 user_id                  | integer                     | not null
 is_at_office             | boolean                     | not null default true
 date                     | date                        | not null
 started_at               | timestamp without time zone |
 ended_at                 | timestamp without time zone |

平日の日中の勤務時間を出すクエリーは下記のようになります。

  • 実際には休憩時間も引く必要があるのでもうちょっと複雑になります
  • DBにはUTCで保存されているので、9時間足しています。
SELECT
  "User Attendances"."user_id" AS "Id",
  EXTRACT(EPOCH
          FROM
          CASE WHEN EXTRACT(DOW FROM date) <> 0
            THEN
              GREATEST(
                  INTERVAL '0',
                  LEAST(
                      (started_at + INTERVAL '9 hours') :: DATE + INTERVAL '22 hours',
                      ended_at + INTERVAL '9 hours'
                  ) - GREATEST(
                      (started_at + INTERVAL '9 hours') :: DATE + INTERVAL '5 hours',
                      started_at + INTERVAL '9 hours'
                  )
              )
          ELSE INTERVAL '0'
          END
          +
          CASE WHEN EXTRACT(DOW FROM date + INTERVAL '1 days') <> 0 AND
                    ((started_at + INTERVAL '9 hours') :: DATE < (ended_at + INTERVAL '9 hours') :: DATE)
            THEN
              GREATEST(
                  INTERVAL '0',
                  LEAST(
                      (ended_at + INTERVAL '9 hours') :: DATE + INTERVAL '22 hours',
                      ended_at + INTERVAL '9 hours'
                  ) - (
                    (ended_at + INTERVAL '9 hours') :: DATE + INTERVAL '5 hours'
                  )
              )
          ELSE INTERVAL '0'
          END
  ) / 60                       AS "月~土通常時間"
FROM "public"."user_attendances" AS "User Attendances"
WHERE ("User Attendances"."date" BETWEEN {CALENDAR.START} AND {CALENDAR.END}
AND ("User Attendances"."status" = 1))

すごいですね...!久しぶりにこんなクエリーを書きました。 でもおかげで、正しい時間数が算出できるようになりました。

WondershakeではSQLを駆使してLocariや会社をよくしてくれる人を探しています! よかったらご応募ください!