Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

制約条件の活用について

こんにちは、スクラムマスターやってます貫名です。

現在弊社ではodd-e Japanアジャイルコーチの江端氏に文字通りコーチング頂き、絶賛チーム開発へのテコ入れを実施しています。この期間の学びは多岐に渡りますが、その中でもタイトルに出した「制約条件の活用」はスクラムマスターとしての立場を取っている私にとって特に影響力が大きなテーマでありましたので、駄文ではありますが体験についてご紹介させて頂きます。(今日書く内容は、実際に経験したことに対して私の解釈を加えた内容となります点ご了承ください)

自分の子供に対して、本人が望むならどんなことでも経験させますか?

仕事終わりに飲みながらなシチュエーションで出てきた類の話ですが、スクラムマスターとしての在り方に関する話をしていて子育てを例に話が深まっていきました。お子さんが居る方は言葉のままに、いない方でも想像の中で良いのでこの質問になんて答えるか考えてみて頂けないでしょうか。

この話の背景には “教えることよりも、本人自らが気づいたことによるその後の学び・成長の大きさ” みたいな話があって、この考えを極端に解釈していた私の回答は

「自分の子供が自ら選択するなら、どんな経験をしたとしても構わない(と言いたい!)」

といったものでした。言い換えれば子供が気づくまで見守り続けるスタンスのつもりなのですが、実際の自分の子育て(現在2歳児の父)を振り返っても、将来子供が大きくなった時を想像しても、まぁそんなはずないなと。超えちゃいけない一線?とでもいうのでしょうか、あるなぁと。

多様な背景を持つ30人弱の集団で結論がなかなか出ない、さぁどうする?

こういった場面に出くわすくことはないでしょうか?30人はなくても、もっと少ない人数・2人であっても議論が平行線をたどってなかなか結論が出せない場面であればOKです。司会だろうと、議論の参加者だろうとどっちでも良いので、あなたならどう振る舞うかを考えてください。

ちなみに私もこういう場面に出くわすことは少なく無いのですが、仕事でこういった場面に出くわした場合によく私がとっていた行動/思考が

「決め方のルールが決まって無いから前進してないはず、なのでルール決めよう」

と考え行動します。どのアプローチが正解か・間違いかという話ではないですが、これも1つの選択だと思っています。ただよくありがちかと思うのがこの「決め方のルール」を決めること自体にも時間をかけてしまうケースです。どんな規模であれ、意思決定をスムーズに進行する集団は、決める上での基準みたいなもの?がはっきりしてるんじゃないかと思います。

「より良いもの」の前の「許容できないもの」

多様な見方ができるとは思いますが、子供への経験の話は “より良い学び・成長” を意図してるわけですし、集団での議論の話も個々人の参加者は “より良い回答” を集団が選ぶよう主張なりをしていると思われます。

この「より良い」がなかなか曲者で、子育てで言えばどのタイムスパンで考えるかによって選択に対する評価は変わるかもですし、集団の議論で言えば参加者一人一人の評価は異なっているのがむしろ普通だったりします。この時に着目してみて良いかも知れないと学んだのが、

「許容できないもの」

です。子育てで言えばどんなに子供が自分で選んだとは言え「他人や自分の命を危険にさらすこと」は冗談抜きで親である私にとっては許容できないことの1つになると思っています。集団の議論の場ではどうでしょう?これは時と場合によって異なってくるので例は割愛させて頂きます。

この「許容できないもの = 制約条件」と私は解釈しているのですが、このラインを適切にひくことでそれ以外の部分では思う存分自由に行動/思考できる状態を作ることこそが、牧羊犬と言い表されるスクラムマスターに求められることの1つかも知れません。

ペアプログラミングの勧め

みなさま、こんにちは。

昨年、11月に中途で入社しました堀越というものでございます。

つい最近のお話ですが、業務でペアプログラミングなるものを実施しました。

良いなと感じる点が多かったので説明も兼ねて共有します。

ペアプログラミング

2人のプログラマが1台のワークステーションを使って共同でソフトウェア開発を行う手法である。一方が単体テストを打ち込んでいるときに、もう一方がそのテストを通るクラスについて考えるといったように、相補的な作業をする。

引用元:ペアプログラミング - Wikipedia

ペアプログラミング実施の背景

弊社では、4月よりスクラムの取り組みを改善する施策をしており、

目標の一つに複数の開発チームがそれぞれが所有する開発タスクを

チーム間を跨いで対応可能にするというものがあります。

ただし、その為には他のチームが抱えるプロダクトや業務に関する知識が備わっている前提となるため、

この課題に対する一部アクションとして他チームメンバとペアプログラミングの実施に至ったという背景があります。

ペアプログラミングを始める前に

1. パートナーを探す

ペアプログラミングは1人ではできません。

勇気を出して気になるあの子を誘ってみましょう。

2. ドライバーとナビゲータに分担する

ペアプログラミングは2人で作業を進めるとはいうものの、

端末を一台しか使いませんのでドライバーとナビゲータを決めましょう。

ドライバはコードを書く人、ナビゲータは作業指示をする人というのが私の理解ですが、

ナビゲータは作業指示ではなく、作業を促すようなコメントをするのが良いとする文献もあるようです。

3. ドライバーとナビゲータの交代サイクル

作業分担が長時間同じだと疲れてしまうので、

ドライバーとナビゲータの交代時間を決めましょう。

私の場合、おおよそ30分間隔だったと記憶しています。

4. 着手する作業を決める

作業を開始してから何するか考えるのは時間がもったいないですね。

あらかじめ着手する作業内容を決めておきましょう。

その際、どこまでの作業をゴールとするのか目標設定しておくと尚良いでしょう

実際にやってみて良いなと思った点

開発Tipsを共有できる

CUI のコマンドや、IDEのショートカットに何を使うか等の

細かいキーボード操作や、仕事の仕方など実際に一人で作業していると

なかなか共有する機会が無いと思います。

ペアプログラミングをしていく中でパートナーの作業風景から

得るものが少なからずあると思います。

私はこの結果、生産性が3倍向上した気持ちになりました。

一人よりも二人のほうが問題解決できることが多い

1人で作業していたら時間がかかっていた、ハマっていたであろうことが、

2人で立ち向かうことでさほど問題にならないことが多いと思います。

何かの問題に直面した際、パートナーに状況を説明することで、

頭が整理されて自己解決したなんてこともあると思います。

プルリクに出すコードの品質があがる

ドライバーがコードを書いている最中、ナビゲータはコードレビューする形になるので、

実装不備があった時点ですぐに指摘することができます。

そのため、プルリクを出す時点である程度品質が担保された状態になります。

所感

個人的には自身の成長につながる部分が多かったのでペアプログラミングお勧めです。

ただし、成果物を出すのに2人分のリソースを割くので全体での時間はかかっているかもしませんし、

一人で作業することを好み、自身の作業スタイルを貫きたいエンジニアにとっては、

ストレスに感じてしまうところもあるかもしれません。

メリット、デメリットを考慮して、必要に応じて実施するのが良いのではないでしょうか。

最後に

僕とペアプロ…、しませんか?

お誘いお待ちしてます。

Solarizedで目に優しいターミナル

こんにちは。エンジニア2年目の大北です!

最近同期がtigを導入していたので、私も入れてみました。 こんな感じです。 f:id:sachipapi423:20170616131239p:plain

tigの機能は素晴らしくて、1行だけgit addしたり、addしたやつを戻したりなどが自由にできるんですが、かなりカラフルで目がチカチカしてきます。。

そこでターミナルの色を綺麗に整えてくれるSolarizedを入れてみることにしました!

続きを読む

GANMA!チームのScrumについて

GANMA!開発エンジニアの盛岡(@morizooo)です。
3月からScrumへの取り組み方を変えており、形になってきたので、
どのような変化があったかを共有したいと思います。

背景

今までの開発手法としてはSCRUM BOOT CAMPを読んだ後に独自で考えた手法で、
以下のように行っていました。

既存の開発で大きな問題はないように感じてはいたのですが、
チームリーダーがScrum AllianceのCSM研修を受講する機会があり、
Scrum Allianceが提唱するScrumについての説明を受け、フィードバックを受ける機会がありました。
そのフィードバックを踏まえて現在の問題点を考えて、
良くなるかもしれないなら新しい手法にTryしてみる価値があるんじゃないかということで挑戦しています。

f:id:morizo999:20170609162552j:plain

現在の開発の流れ

  • 1週間Sprint
  • タスクのアサインはしない
  • タスクの詳細をチームで考えて30min/60minの粒度に分割する

スケジュール

1週間Sprintを下記のスケジュールでセレモニーを行っています

  • 毎日 17:00~17:15 Daily Scrum
  • 火曜 16:00~17:00 Product Backlog Refinement
  • 木曜 13:00~13:30 Sprint Review
       13:30~14:30 Sprint Retrospective
       15:00~18:00 Sprint Planning
  • 金曜 10:00~13:00 Sprint Planning 予備時間(木曜で終わらない場合の延長時間)   

Sprint Planning

一番大きく変わった部分がSprint Planningなので、このセレモニーを自分たちのやり方と合うように、
どのように実行しているかを説明します。

  1. POともに次スプリントの開発するタスクを決める(この段階では詳細検討は行われていない)
  2. 次スプリント中にやらなくてはならない技術タスク(開発チーム管理のタスク)を次スプリントのタスクに追加する
  3. ポイント振りはProduct Backlog Refinementで終わっているはずだが、
    
緊急対応などの何らかの理由で見積もっていない物があればこの場で見積る
  4. チーム全員でDDDをやっているので、
    ドメイン設計・テーブル設計等全体で考慮するタスクがないか確認する。
    全体で考慮するタスクが存在する場合は全員で検討する
  5. 詳細検討を行いチーム全員でタスクの分割を行う
  6. 詳細検討が終わり次第、POに確認をしてもらい作業開始。1人1日4時間を予定時間として計算する。
    詳細見積もりで予定時間を超過しているようであれば、スプリントの開発タスク量を減らす相談をPOと行う。

詳細検討について

詳細検討後は30min/60minのタスクしか存在せず、チーム誰でもできるような作業に分解します。
(現状は、Android/iOSのプラットフォームの違いがあるので全員が出来る状態ではありませんが、誰でもできる状態を目指しています)
開発優先度の高いタスクから以下の作業を、一番そのタスクに一番詳しい人から行っています。

  1. タスクの内容を確認する
  2. 空コミットでチケット番号をいれてブランチを作成する(Bambooでコミットログでトラッキングできるため)
  3. 必要なタスクをチーム全員でソースコードを見ながら30min/60minで出来るチケットを作成する
  4. 変更が必要な所にTODOコメントをつけてプッシュ
  5. 一通り切り終わった後に、全員が出来るように曖昧な点がないか確認し実装イメージを共有する

方針が立たないようなタスクの場合には、以下の方法を使う場合もあります。

  1. 各々が自分で思う作業順を付箋に書き出す
  2. 説明しながら付箋をホワイトボードに貼る
  3. 全員が貼り終わったら、分類分けをする
  4. タイトルを付け、30min/60minかを相談する

www.ted.com

所感

現在もまだまだ完成しておらず、Sprint Retrospective毎にルールが進化しています。
最初はSprint RetrospectiveやSprint Planningだけで一日が終わる日があり、
エンジニアなのにコード全然かけてないぞ〜?という日々を過ごしていたのですが、
現在はある程度高速化が進んでおり、上記のスケジュール感で回るようになってきました。 30min/60minの詳細に分割するのは大変なのですが、体調不良等で急に休むことになってしまった時や、
思うように進まなくてハマっている場合でもタスクの渡しやすくなったので、チームとして助け合いできる環境になったと思います。  

今後もScurmをやっていくぞー!というわけではなく(Scrum Slavesにならない)、
チームとして最適な手法を模索していきたいと考えています。

というわけで、開発手法含めて一緒に考えて開発する人を募集していますので、よろしくお願いします! www.septeni-holdings.co.jp

ScalaでDeeplearning4jを使い自動運転で峠を攻める!

こんにちは。菅野です。

最近、AIとか機械学習とかが話題ですね。
AIに仕事を奪われる職業がどうとかの記事もよく見かけます。
このブログ記事もAIが書いてくれたら良いのにと思っている今日この頃です。

…でも思ってるだけでは仕事を奪ってくれないので、やっぱり何かしら自分で作るしか無さそう。
という訳で、今回はJavaディープラーニングが出来るDeeplearning4jを使って機械学習を試します!

プロジェクトD

さて、何を作りましょう?
最終的には私の仕事を勝手にやってくれるAIを作りたいです。
でも、はじめは簡単なものから少しずつ作っていこうと思います。
よくディープラーニングでネタにされるのは手書き文字の識別ですが、正直面白くないので道路上を自動運転するAIを作ります!

続きを読む

vue-playとvue-styleguide-generatorでVueコンポーネント開発

こんにちは、丸山です。

JavaScriptフレームワークのひとつVue.jsの主な機能のひとつに、コンポーネントがあります。
コンポーネントはUIの部品のようなもので、(最初から特定の機能のために使われることが想定されている場合以外は)なるべく独立して再利用可能な状態を保つべきです。
また、そういったコンポーネントは作った人以外が使用することも多々あるので、どのように使うか、どういう機能を提供しているかといった情報が簡単にわかれば、開発の手助けになるかと思います。

そこで今回は、そういった場面でVueコンポーネントの開発を助けるライブラリを紹介します。

サンプルコンポーネント

今回下記のようなコンポーネントを用意しました。内容は簡単なMarkdownエディタです。

<template>
  <div id="editor">
    <textarea v-model="input"></textarea>
    <div v-html="markedText"></div>
    <button v-on:click="deleteText">delete</button>
  </div>
</template>

<script>
  var marked = require('marked')

  export default {
    name: 'marked-editor',
    props: {
      defaultInput: {
        type: String,
        default: 'Hello!'
      }
    },
    data () {
      return {
        input: this.defaultInput
      }
    },
    computed: {
      markedText: function () {
        return marked(this.input)
      }
    },
    methods: {
      deleteText: function () {
        this.input = ''
      }
    }
  }
</script>

<style scoped>
  html, body, #editor {
    margin: 0;
    height: 100%;
    font-family: 'Helvetica Neue', Arial, sans-serif;
    color: #333;
  }

  textarea, #editor div {
    display: inline-block;
    width: 49%;
    height: 100%;
    vertical-align: top;
    box-sizing: border-box;
    padding: 0 20px;
  }

  textarea {
    border: none;
    border-right: 1px solid #ccc;
    resize: none;
    outline: none;
    background-color: #f6f6f6;
    font-size: 14px;
    font-family: 'Monaco', courier, monospace;
    padding: 20px;
  }

  code {
    color: #f66;
  }

  button {
    margin-right: 50px;
  }
</style>

f:id:to_maruyama:20170531223149g:plain

vue-play

vue-playコンポーネント単位でのデモを可能にするライブラリです。
コンポーネントのデモができることで、そのコンポーネントが単独で動くことを保証したり、そのコンポーネントの使い方を知ることができます。

まず、

yarn global add getplay

でインストール後、自分のvueのプロジェクト内に移動します。そこで、

getplay

を実行すると、playフォルダがプロジェクト内に作成されます。このフォルダ内にあるindex.jsを次のように修正します。

import Vue from 'vue'
import { play } from 'vue-play'
import MarkedEditor from '../src/components/MarkedEditor.vue'

Vue.component('marked-editor', MarkedEditor)

play('MarkedEditor')
  .name('MarkedEditor')
  .displayName('The MarkedEditor Component')
  .add('default', { template: '<marked-editor></marked-editor>'})
  .add('Pass parameter', { template: '<marked-editor defaultInput="# Hello!"></marked-editor>'})

MarkedEditor.vueはサンプルコンポーネントのファイルです。
ここにはデモをする対象のコンポーネントの登録と、どのようなデモを行うか(シナリオ)を書きます。今回はパラメータを何も渡さずデフォルト状態で表示させるデモとパラメータを渡す場合のデモを書きました。

上記の修正を行ったら、

npm run play

を実行して、http://localhost:5000/にアクセスします。

f:id:to_maruyama:20170531231315g:plain

ブラウザにvue-playの画面が表示されます。index.jsに書いたシナリオが左側に表示され、そこをクリックすることで、デモの内容が表示されます。

vue-styleguide-generator

vue-styleguide-generatorコンポーネントの一覧とそれぞれの概要が書かれたhtmlファイルを出力するライブラリです。
コンポーネントの情報をまとめておくことで、開発者はソースを読み込む前にそのコンポーネントのことを把握することができます。

npm install vue-styleguide-generator --save-dev

でインストール後、

node ./node_modules/vue-styleguide-generator/ --src src/compone
nts/ --dest preview

を実行します。
--srcで指定しているのは情報を取得したいコンポーネントがあるディレクトリ、--destで指定しているのは、コンポーネントの情報が書かれたファイルを出力する先です。
実行後、プロジェクト内にpreviewフォルダが作成されます。その中にindex.htmlがあるのでこれをブラウザで表示させれば、コンポーネントの情報が見れます。 さらに、コンポーネントと同じ階層に.mdファイルを作成し、そのなかにコンポーネントのさらに詳細な情報を書くこともできます。

今回src/components/配下は以下のようになっています。

src
└── components
     ├── MarkedEditor.vue
     └── README.md

README.md

このコンポーネントはMarkdownエディタです

のみが書かれているファイルです。

以下がブラウザで表示したindex.htmlです。

f:id:to_maruyama:20170531233902p:plain

画面にはコンポーネントのプロパティやメソッド等の情報が表示されます。.mdファイルに書いた情報はReadmeに表示されます。

参考

Vue.js Component Style Guide

ScalaでAndroid開発をする方法 / セプテーニ技術読本(2017) PDF配布

こんにちは!杉谷と申します。 セプテーニグループのセプテーニ・オリジナル社とコミックスマート社のCTOを務めています。

AndroidがKotlinを正式サポートというニュースが駆け巡っています。

同じJVM系のScalaはどうなの?Androidで使えるの?と思われた方もいらっしゃると思いますが、 実は GANMA!Android版はScalaで作られています。 GANMA!はダウンロードも600万を超え、Google Play上でも高評価を頂戴しておりますので開発実績としては十分な物かと思われます。

本エントリではScala Matsuri 2017Scala将軍達の後の祭り2017で配布したプライズ同人誌 「セプテーニ技術読本(2017)」のPDFと、その中で掲載されたScalaAndroid開発」をお送りします。


本稿は2017年2月に配布されたプライズ同人誌 《セプテーニ 技術読本(2017)》 に収録されたものです。PDFはエントリの最後からダウンロードできます。

ScalaAndroidアプリ開発

はじめに

GANMA!のAndroid版はScalaを使って開発をしています。

皆様がまず気になるのは「ぶっちゃけ(Scalaで作って)どう?」だとおもいますが、 結論からいうと「動くし快適だが、途中が険しくてオススメしづらい」といった代物です。

GANMMA!のダウンロード数は400万を超え(※2017.2現在 / 2017.5時点では600万 )、アクティブユーザーもかなりの人数となっています。 安定性も高い状態を保てており、今のところGoogle Play上でも高評価を得られています。 Scalaでの開発実績としては十分なものかと思われます。

本稿では、ScalaAndroid開発を行ってみたい方のお役に立てるよう、 Android Studioを使った開発環境の構築方法をステップバイステップでご紹介いたします。

なぜScalaを使うのか?

繰り返しになりますが、ScalaでのAndroid開発は導入は面倒だし、将来性には難があります。 それでもその辛みを補って余りある嬉しさがあります。

  • 楽に書ける Scalaの豊富な記述性が醸し出す書き味は大変に素晴らしい物です。 標準CollectionAPIの充実、traitによる型表現の広がり、Scalaエコシステムの様々なライブラリ、どの要素も日々の開発を楽にしてくれます。
  • Future美味しい Android開発では通信をしてからUI更新、といった非同期処理をよく書きますが、 ScalaのFutureは快適に非同期処理が記述できます。Java/Kotlinでもライブラリを使えばFuture/Promiseは扱えますが、Scalaでの書きやすさにはかなわない印象です。Akkaも利用すれば、非同期処理を扱う手段はさらに広がるでしょう。

我々の場合、サーバはScalaで開発しているのと、1エンジニアがサーバもクライアントも区別無くいじる文化なので クライアント側もScalaにしておきたかった、という都合からScalaでのAndroid開発を始めたのですが、とても楽に開発できています。

Scalaと比較するならKotlinでしょうか? 実際の所Kotlinは深くは触っていないため、比較はできないのですが Kotlin公式サイトによる “Scalaとの比較"には

The main goal of the Kotlin team is to create a pragmatic and productive programming language, rather than to advance the state of the art in programming language research. Taking this into account, if you are happy with Scala, you most likely do not need Kotlin. (訳:Kotlinチームの主な目標は、プログラミング言語の研究における最先端技術を進歩させるのではなく、実用的で生産的なプログラミング言語を作成することです。これを考慮すると、あなたがScalaに満足しているのであれば、Kotlinはおそらく必要ないでしょう。)

と書かれています。(2017.5追記 比較ページは消された様子、 https://github.com/JetBrains/kotlin-web-site/blob/7e8e56a7a40f37b37e2e66c77fbbdf5484927b27/pages/docs/reference/comparison-to-scala.md に残っています)

Javaから移行する場合、Scalaが未経験であればKotlinでも十分な幸福さを得られると思いますが、 Scalaに慣れている場合は、導入の面倒さや将来性の不安を押してでもScalaで開発をする価値があるでしょう。

sbt-androidとgradle-android-scala-pluginの比較

Androidの標準ビルドシステムはGradleですが、Scalaでよく使われるビルドシステムはsbtです。 ScalaAndroid開発を行うアプローチは

の二つが主な方法となります。

sbt-androidは開発が活発で動作が安定していますし、ドキュメントも充実しています。Instant Runも利用できるようです。

ただし、ビルドシステムがAndroid標準ではないので、プロジェクトをAndroid StudioやIDEAで扱うには一手間あるほか(実行設定をsbtを通した物にする必要がある等) テストやデバッガのIDE連携が出来ない1、などAndroid統合機能との連携に難があります。

gradle-android-scala-pluginはIDEAやAndroidStudioで特別な設定無しに扱えるのが大きなメリットです。 プロジェクトをインポートするだけで全てのAndroid統合機能が利用できます。デバッガやInstantRunも通常通り使えます。 特に動かしたいユニットテストGUIから選んで単独実行できる点が大変に便利です。

ただし開発が活発ではない、というか止まっているという強烈な問題があります。

MavenのCentral Repositoryに登録されているバージョンでは 新しいGradleが利用できず、最新のAndroid Build Toolも使えない、ビルドも遅い、という問題があるため、 対応が進んでいるmasterブランチのHEADを取得し自力でビルドを行う必要があります2Android界の進展は早いので、将来にわたって利用できるかどうかが懸念となります。

我々はそれでも使い勝手が良く、テストを実行しやすいgradle-android-scala-pluginのほうを利用しています。

プロジェクト作成

それでは実際にAndroid Studio 2.2.3を使って簡単なAndroidアプリを作れるようになるまで、 gradle-android-scala-pluginのビルドからステップバイステップで紹介いたします。

gradle-android-scala-pluginのビルド

最初にGradleのインストールします。 インストールにはJVM系のインストールマネージャであるSDKMANを利用します。

以下のコマンドでインストールされます。

$ curl -s get.sdkman.io | bash

次のコマンドを実行し初期設定を行います。

$ source "$HOME/.sdkman/bin/sdkman-init.sh"

次にgradleをインストールしますが gradle-android-scala-pluginは2017年の1月のmaster:HEADでは2.14.0,2.14.1ではビルドできなかったので、 対応とされている2.12をインストールします。3

$ sdk install gradle 2.12

gradle-android-scala-pluginをcloneします。

$ git clone https://github.com/saturday06/gradle-android-scala-plugin.git

ビルドします。

$ cd gradle-android-scala-plugin/
$ gradle assemble

./build/libs/gradle-android-scala-plugin-1.5-SNAPSHOT.jarプラグインのパッケージが作成されます。 後ほどAndroidプロジェクト作成時に、このJARを利用します。

Android Studioのセットアップとプロジェクト作成

Android Studioをインストールし実行します。 初回起動時にインストール方法(Install Type)を聞かれますが、Standardで進めます。

Welcome to Android Studioの画面が表示されたら、メニューの Configure > Plugins を選び Install JetBrains plugin画面から、Scalaプラグインをインストールします。

インストールが終わったら、 Android Studioを再起動し、再びWelcome画面から Start a new Android Studio projectを選び新規プロジェクトを作成します。 プロジェクト名はお好みで大丈夫です。本稿ではScalaHelloWorldという名前で~/AndroidStudioProjects/ScalaHelloWorld/に作成するとします。企業ドメインsepteni-original.co.jp(=Package Nameがjp.co.septeni_original)とします。

Minimum SDKScala自体はAndroid 2.x系が対象でも利用できなくはないですが、苦痛がとても大きいので4.0.3以上を推奨します。

Add an Activity to MobileEmpty Activity を選択します。

Customize the Activityは初期値のままで進めます。

これで~/AndroidStudioProjects/ScalaHelloWorld/にプロジェクトが作成されました。

プロジェクトにgradle-android-scala-pluginを導入

プロジェクトのディレクトリ(~/AndroidStudioProjects/ScalaHelloWorld/)に 先ほど作成したgradle-android-scala-plugin-1.5-SNAPSHOT.jarをコピーしbuild.gradleで利用指定をします。(以降、ファイル名を表すとき ~/AndroidStudioProjects/ScalaHelloWorld/を省略)

build.gradle

buildscript {
    …
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath files("gradle-android-scala-plugin-1.5-SNAPSHOT.jar")
        …
    }
    …
}

次にapp/build.gradleプラグイン利用指定を行います。

…
apply plugin: 'com.android.application'
apply plugin: "jp.leafytree.android-scala"

依存ライブラリにScala 2.11.8を追加します。4

app/build.gradle

dependencies {
    …
    compile 'org.scala-lang:scala-library:2.11.8'
    …
}

これで最低限のプロジェクト設定はできました。

HelloWorld

Android Studioによって作られたMainActivity.javaScalaで書き直してみましょう。

app/src/main以下にディレクトscalaを作成します。

app/src/main/scala以下にパッケージjp.co.septeni_original.scalahelloworldを作成(=jp/co/septeni_original/ディレクトリを作成)します。

app/src/main/scala/jp/co/septeni_original/MainActiviy.scalaを作成します。

f:id:ysugitani:20170519103826p:plain

MainActiviy.scalaの編集画面上部にNo Scala SDK in moduleと出てくるので、Setup Scala SDKを押下し、Scala 2.11.8を追加します。

MainActivity.scalaMainActivity.javaと同等の記述をします。

MainActivity.scala

package jp.co.septeni_original.scalahelloworld

import android.os.Bundle
import android.support.v7.app.AppCompatActivity

class MainActivity extends AppCompatActivity {
  
  override def onCreate(savedInstanceState: Bundle): Unit = {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }

}

Java側(app/src/main/java以下)にあるMainActiviy.javaを削除します。

これで実装は完了です。

メニューのRun > Run app、もしくはメニューバーのRunボタンを押し、 Android5.0(API Level 20)以上5エミュレーターを作成し、実行します。

f:id:ysugitani:20170519103846p:plain

デバッグ実行を行えばデバッガも利用できます。

テスト

次に、MainActivityと同じように、Android Studioにより作成された実機で実行する単体テスト(ExampleInstrumentedTest.java)のほうもScalaで書き直してみましょう。残念ながらspecs2等は使えずJunit4となります。

app/src/androidTest以下にディレクトscalaを作成します。

app/src/androidTest/scala以下にパッケージjp.co.septeni_original.scalahelloworldを作成します。

app/src/androidTest/scala/jp/co/septeni_original/ExampleInstrumentedTest.scalaを作成します。

f:id:ysugitani:20170519103901p:plain

ExampleInstrumentedTest.scalaExampleInstrumentedTest.javaと同等の記述をします。

ExampleInstrumentedTest.scala

package jp.co.septeni_original.scalahelloworld

import android.content.Context
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Assert._
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(classOf[AndroidJUnit4])
class ExampleInstrumentedTest {

  @Test
  def AppContextを使える(): Unit = {
    val appContext: Context = InstrumentationRegistry.getTargetContext
    assertEquals("jp.co.septeni_original.scalahelloworld", appContext.getPackageName)
  }
}

Java側(app/src/androidTest/java以下)にあるExampleInstrumentedTest.javaを削除します。

プロジェクトナビゲーター側からExampleInstrumentedTestを右クリックし、 Run ‘ExampleInstrumentedTest’からテストを実行します。

f:id:ysugitani:20170519103917p:plain

MultiDex

次にAPI Level 20未満でも実行できるようにMultiDex6の設定を行います。

app/build.gradle に以下の設定を追記します。

android {
    defaultConfig {
      …
        multiDexEnabled true
      …
    }
}
dependencies {
    …
    compile "com.android.support:multidex:1.0.1"
    …
}

MultiDex処理を実行するApplicationクラスを新規作成します。7

app/src/main/scala/jp/co/septeni_original/scalahelloworld/ScalaHelloWorldApplication.scala

package jp.co.septeni_original.scalahelloworld

import android.app.Application
import android.content.Context
import android.support.multidex.MultiDex

class ScalaHelloWorldApplication extends Application {
  override protected def attachBaseContext(base: Context): Unit = {
    super.attachBaseContext(base)
    MultiDex.install(this)
  }
}

このApplicationクラスが実行されるようにManifestに追記します。

app/src/main/AndroidManifest.xml

<application
        android:name=".ScalaHelloWorldApplication"

一番最初のdexファイル(classes.dex)にはScalaHelloWorldApplicationクラス自身と、 ScalaHelloWorldApplicationクラスが利用するクラスが含まれる必要があります。

次にこれを指定するファイルをapp/multidex.keepに記述、設定します。

android/support/multidex/BuildConfig.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/MultiDex$V4.class
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/ZipUtil$CentralDirectory.class
android/support/multidex/ZipUtil.class
jp/co/septeni_original/scalahelloworld/ScalaHelloWorldApplication.class

以降、ScalaHelloWorldApplicationクラスが利用するクラスが増えたときはこのファイルに追記する必要があります8

multidex.keepが読み込まれるよう、build設定を変更します。

app/build.gradle

android {
    …
    dexOptions {
        additionalParameters = [
                '--multi-dex',
                "--main-dex-list=$projectDir/multidex.keep".toString(),
                '--set-max-idx-number=45000'
        ]
    }
    …
}

additionalParametersの--set-max-idx-number=45000--multi-dexは本来は不要なのですが、 Android4.0.x系では1dexに65535まで詰め込むと読み込めない事があるので、4.0.x系を動作対象に入れる場合は--set-max-idx-number=45000(1dex45000までに制限する)を指定したほうが安全です。 また我々の環境では--multi-dexも設定しておかないとInstant Runが正しく動作しなかったので指定しています。

以上でMultiDexの設定は完了です。API Level 15あたりのエミュレーターを作成し、動作確認を行いましょう9

独自Applicationクラスの注意点

gradle-android-scala-pluginや元々のMultiDexのREADMEでは、独自のApplicationクラスを作る場合は以下に注意するよう案内されています。

  • static field(Scalaであればclassやobjectのコンストラクタ)はMultiDex#installより先に呼ばれるので最初のclasses.dexに含まれる(multidex.keepに記述する)ようにしましょう。
  • Applicationクラスのメソッドが実行されるとき、Applicationクラスより後に読まれる他のクラスのへのアクセス権がない可能性があります。以下のようにRunnableなど、別のクラスを噛ませると問題を回避できます。
  override def onCreate = {
    super.onCreate

    val context = this
    new Runnable {
      override def run = {
        // 独自の処理をここに記述。
        // thisの代わりにcontextを使うこと
      }
    }.run
  }

この場合、multidex.keepにいちいち利用するクラスを記述する必要がありません。

通しで記述すると以下のようになります。

app/src/main/scala/jp/co/septeni_original/scalahelloworld/ScalaHelloWorldApplication.scala

package jp.co.septeni_original.scalahelloworld

import android.app.Application
import android.content.Context
import android.support.multidex.MultiDex

class ScalaHelloWorldApplication extends Application {

  override def onCreate(): Unit = {

    super.onCreate()

    val context = this
    new Runnable {
      override def run(): Unit = {
        // 独自の処理をここに記述。
        // thisの代わりにcontextを使うこと
      }
    }.run()
  }

  override protected def attachBaseContext(base: Context): Unit = {
    super.attachBaseContext(base)
    MultiDex.install(this)
  }

}

Futureを利用する

ScalaAndroidを開発する大きな理由の一つにFutureとPromiseがあります。 もちろんJava/Kotlinでもライブラリを使うなり作るなりすれば可能ですが、Scalaでの書き味の良さには及ばない印象があります。

通信などでFutureを利用する場合、大体の場合「通信をするFutureを作る→.andThenや.onCompleteでUIを更新する」 といった処理になるのですが、AndroidではUI操作はUIスレッドで行う必要があるので、 最後のUI更新はUIスレッドで行う必要があります。

この処理をExecutionContextでラップし使い勝手を良くするgistを ScalaMatsuri座長の麻植さん(@OE_uia)が公開されています。

これを利用すると、FutureコールバックはUIスレッドで実行する処理は、以下のように記述できます。

val tv = new TextView(this)
val x: Future[Long] = future { … }
…
val mine = new UIExecutionContext(this)
x.onComplete { // onSuccessはScala2.12からdeprecatedなのでご注意
  case Success(count) => tv.setText(tv.getText + " onSuccess!:"+count.toString )
  case Failure(t) => …
}(mine)

また、場合によっては、次のようなサポートクラスを利用しています。

object ThreadUtil {

  def runOnUiThread(exec: => Unit): Future[Unit] = {
    val result = Promise[Unit]()

    def execute(): Unit = {
      Try {
        exec
      }.map { _ =>
        result.success(Unit)
      }.recover {
        case e: Throwable => result.failure(e)
      }
    }

    if (Looper.myLooper() == Looper.getMainLooper) {
      execute()
    } else {
      val handler = new Handler(Looper.getMainLooper)
      handler.post(new Runnable {
        override def run(): Unit = execute()
      })
    }

    result.future
  }
}

次のように利用します。

ThreadUtil.runOnUiThread(tv.setText("ほげ"))

付録: ProGuardについて

アプリをリリースするときProGuardをかける場合があります。

設定方法は通常と変わりませんが、Scalaのライブラリを多数除外指定する必要があります。

pfn氏作の sbt-androidのほうで容易されている android-proguard.config をたたき台に調整していくと便利です。

付録: dexに格納されているクラス一覧を確認する方法

apkファイルをunzipし、出てきたdexファイルに対してdexdumpをする事によって確認できます。

$ cd app/build/outputs/apk
$ unzip app-debug.apk
$ dexdump *.dex | grep 'Class descriptor'
  Class descriptor  : 'Landroid/support/annotation/AnimRes;'
  Class descriptor  : 'Landroid/support/annotation/AnimatorRes;'
  Class descriptor  : 'Landroid/support/annotation/AnyRes;'
  Class descriptor  : 'Landroid/support/annotation/AnyThread;'
  Class descriptor  : 'Landroid/support/annotation/ArrayRes;'
  Class descriptor  : 'Landroid/support/annotation/AttrRes;'
  Class descriptor  : 'Landroid/support/annotation/BinderThread;'
  Class descriptor  : 'Landroid/support/annotation/BoolRes;'
  …

終わりに

如何でしたでしょうか?

ご紹介させていただいたとおり、使えるようになるまでがの道が険しく、将来の不安も残る方法です。大体の場合はKotlinを選ぶ方が良いでしょう。

しかしそれでもScalaを使う価値はあります。 本稿がScala愛のある方のお役に立てると幸いです。

《杉谷保幸》


セプテーニ技術読本 2017

f:id:ysugitani:20170519102932j:plain:w300

ダウンロードはこちら


  1. 我々が動かす方法が見つけられなかっただけなので、方法はあるのかもしれません。

  2. メンテナとして名乗り出ようかな、と考えています。

  3. この場面以外なら新しいバージョンのGradleを利用できます。動作速度が向上しているのでより新しいバージョンを使った方が良いでしょう。

  4. Android SDKは現時点ではJava7バイトコードまでしか対応していないため、Scala2.12はまだ利用できません。

  5. Scalaを導入するだけでクラス数は1dex上限の65535を超えてしまうので、Android5.0(API Level 20)未満ではまだ動かせません。後述するMultiDex設定が終わるまではAndroid5.0以上を利用します。

  6. MultiDexに関する詳細説明は割愛します。

  7. 実際にはここまで単機能であれば、android.support.multidex.MultiDexApplicationを使うか継承するで十分なのですが、後ほど拡張するので本稿では自分で定義をしました。

  8. 追記する場合、ビルド時に生成されるbuild/intermediates/multi-dex/debug/multidexlist.txtを参照すると探しやすいです。

  9. 最初のdexに格納されているclassを確認したい場合、後述の付録: dexに格納されているクラス一覧を確認する方法をご参照ください。