読者です 読者をやめる 読者になる 読者になる

Androidアプリのリソースを整理して開発効率を改善した話

Android

技術部モバイル基盤グループの児山です。

モバイル基盤グループではモバイルアプリの開発だけでなく、開発環境の整備や開発効率の向上も重要な目的の一つとしています。 今回はその取組の中で、特にAndroidアプリの開発効率向上に関する取り組みを紹介したいと思います。

開発効率を下げる要因

経験上、どのようなアプリでも開発を続けていくうちに細かい技術的な負債がたまり、開発効率は下がっていくものです。 クックパッドアプリでは朝Lintの実施やDokumiによるレビューによってソースコードに技術的負債を溜め込まないよう心がけてきましたが、画像やレイアウトなどのリソースファイルは無法地帯になりつつありました。 以下にクックパッドアプリがかかえていた、リソース関連の問題をいくつか挙げてみます。

themeが整備されていない

Androidアプリでは、themeを定義し適用することで画面全体のUIを一括で設定することができます。 しかし、これまでのクックパッドアプリでは規則なく命名されたtheme定義が複数のファイルに散らばっており、どのthemeがどのようなUIを提供しているのか、代替リソースによって何が違うのか確認しづらい状態になっていました。 このため、開発効率の低下だけでなく、新しい画面を追加する際に既存のthemeとほぼ同じthemeを定義してしまうなど新たな負債を生む原因にもなっていました。

styleによるデザインの再利用ができていない

AndroidアプリではいくつかのUI属性をまとめてstyleとして定義し、そのstyleを複数のViewに適用することで、それぞれのViewの見た目を簡単に統一することができます。 しかし、styleの命名規則が定まっていなかったり文字サイズや文字色に決まったルールがないといった問題でstyleの再利用がうまくいかず、結局同じエンジニアが作成した2,3個のlayout間でしか再利用されていないということがよくありました。

文字の色、サイズ、書体などが整理されていない

アプリのデザインにおいて、全体の統一感を高めるためにも文字の色やサイズ、書体は表示箇所によって正しく使い分ける必要があります。 Androidアプリでは先述のstyleを利用することで使い分けが簡単にできるようになっていますが、クックパッドアプリではアプリ全体に関する標準的な文字サイズという定義がなかったため、各画面バラバラに定義されている状態でした。 その結果、文字サイズの直接指定箇所はレイアウトファイル内だけで400箇所以上、文字サイズのdimen定義は50個以上存在し、ほとんど再利用されないまま放置されていました。

エンジニアとデザイナの間に共通言語がない

styleの命名規則が統一されていなかったため、デザイナとエンジニアのやりとりはこの部分のテキストサイズが何sp、Boldで…と属性ごとの指定で行っていました。 標準となる文字サイズも決まっていなかったため1sp刻みで文字サイズを調整することもあり、「なぜこのサイズなのか」という疑問に明確な答えがだせずにもやもやしながら修正したこともあります。 クックパッドアプリでも、このエンジニア・デザイナ間のやりとりが一番開発効率を下げてしまっているようでした。

開発効率を上げるための工夫

ここまで書いてきたように、デザイン面の開発効率が下がって辛い感じになっていたクックパッドアプリですが、一番の問題は命名規則の混乱とそこから来る再利用性の低さにあることがわかりました。 これを解消するため、クックパッドアプリでは以下のような対応を段階的に進めていきました。

未使用のリソースを削除する

まず最初に、すでに使われなくなっているリソースを探して削除しました。 gradle plugin 1.4からはshrinkResourcesというオプションでビルド時に未使用リソースを削除することができるようになりましたが、開発中のファイル検索などでは普通に出てくるため残しておくと開発時に目にするリソース名が増えてしまい開発効率が下がります。

不要リソースの一覧は先ほどのshrinkResourcesの結果でも表示されますが、Android StudioでAnalyze -> Run Inspection by name -> Unused resourcesを実行することでも表示できます。

これらの方法ではdrawablelayoutのようなファイル単位ではなく、stringdimenstyleなどからも探すことができて非常に便利です。 実際にクックパッドアプリでは未使用リソースの整理だけで30個以上の未使用ファイルと1300行程度のリソース定義を削減することができました。

themeの定義

次に、themeの定義内容の整理を行いました。 先述の通りクックパッドアプリには元々いくつかのthemeが定義されていましたが、命名規則がばらばらであったり定義されているファイルがまちまちになっているという問題がありました。 また、タブレットやTVの対応を行ったりminSdkVersionを引き上げた際に代替リソースの整理をしておらず、すでに参照されなくなっている代替リソースが手付かずで残ったりもしていました。 これを修正するために、以下のような手順でリファクタリングを進めました。

  1. themeの定義をtheme.xmlに集約する(代替リソースの定義もtheme.xmlというファイルに集約する)
  2. themeの名前を規則性のある名前(AppTheme.*など)に揃える
  3. 直接指定するthemeと代替リソースを定義するthemeを分ける

1,2は通常のリファクタリングで行う作業と同じなので割愛し、3について説明します。

themeでは新しく追加された属性を使用したり、タブレットの場合は画面をダイアログのように表示したい、という場合にAPIレベルや画面サイズに応じて異なる記述が必要になることがあります。 これは代替リソースという仕組みを通じて切り替えることが可能ですが、代替リソースは差分のみうまく適用してくれるような仕組みなっていないため、同じthemeの代替リソースを定義すると本来必要な差分だけでなく共通部分もそれぞれ書く必要があります。

values/theme.xml

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:textColor">@android:color/holo_red_dark</item>
</style>

values-*/theme.xml

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- 本来必要な差分 -->
    ....

    <!-- この行の定義も必要 -->
    <item name="android:textColor">@android:color/holo_red_dark</item>
</style>

これでは同じ内容の記述が分散してしまうので、一旦 代替リソースの実装はBase.AppThemeのような名前で行うようにします。 その上でAppThemeという代替リソースを持たないthemeを新しく定義し、Base.AppThemeparentに指定して継承させます。 最後にすべての代替リソースで共通な部分をAppThemeに集約することで、代替リソースで定義すべき差分のみをvalues-*配下のtheme.xmlに集約することができました。

values/theme.xml

<style name="AppTheme" parent="AppTheme">
    <item name="android:textColor">@android:color/holo_red_dark</item>
</style>


<style name="Base.AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" />

values-*/theme.xml

<style name="Base.AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- 本来必要な差分 -->
    ....
</style>

このようにthemeの定義を効率化することで、冗長な記述の削減しつつ参照すべき場所を限定することができ、開発効率を上げることができます。 (appcompatライブラリで提供されているTheme.AppCompat系のthemeも代替リソースをいくつも継承した構成になっており、効率化されていることがわかります)

styleの整理

styleについては記述箇所の整理と名前の整理を行いました。

styleはとても大雑把に分けるとthemeの属性に適用されるstyleと、layoutに適用するためのstyleに分類できます。 この2つが同じstyles.xmlに含まれていると、滅多に触らないthemeをいじるためにtheme.xmlstyles.xml両方を見る必要が出てきてしまい面倒です。 themeに適用するためのstyleはtheme.xmlに含めてしまうほうが良いでしょう。 稀にthemeとlayout両方で使われるstyleもありますが、その場合はthemeの属性に適用するstyleとしてtheme.xmlに含めたほうが良いと思います。

また、layoutに適用するstyleでは名前による継承をうまく利用することも大事です。 名前による継承とは、style名LargeTextというものを定義されている時、LargeText.Boldというstyleは自動的にLargeTextで定義された属性を引き継ぐことができるという機能になります。 例えば以下のように定義した場合、LargeText.Boldは自動的にテキストサイズ20spとなります。

<style name="LargeText">
    <item name="android:textSize">20sp</item>
</style>

<style name="LargeText.Bold">
    <item name="android:textStyle">bold</item>
</style>

残念ながらこれらの作業を簡単にやる方法はないので、定義済みのstyleに対して継承をうまく使いながら属性を減らす作業で地道に整理していくことになります。

その他のリソース整理

theme/style以外のリソースとして、color、dimenを整理しました。

colorの定義でも名前の付け方には注意する必要があり、画面名や機能名で付けてしまうと後々再利用しても良いかどうかわからなくなり多重定義されてしまう可能性が高くなります。 クックパッドアプリでは特によく使う色に関してはgreenorangeなどのわかりやすい名前で定義し、それがレシピ名のような特別な意味を保つ場合は<color name="recipe">@color/green</color>のように再定義しています。 この方法は一見複雑に見えますが、エンジニアからはレシピ名の色を探しやすく、デザイナーからは定義済みの色の種類をみつけやすくするための工夫になっています。

dimenに関しては、View要素のサイズ指定のどこまでをdimenとして定義するか悩ましく、クックパッドアプリでもまだまだ整理の途中となっています。 しかし、テキストサイズに関しては確実にdimenに集約しておくべきだと言えます。 クックパッドアプリではdimenの整理にあたって既存の文字サイズの定義を一旦捨てて、文字サイズをExtraLarge/Large/Default/Small/ExtraSmallの5段階に分けることにしました。 これまでのdimenを新しい5段階にあわせて分類する作業は大変でしたが、このルールを決めてからテキストサイズの指定時に何sp/何dpという混乱が起きなくなりました。

また、colorとdimenの整理にあわせてTextAppearance(TextAppearanceを継承したstyle)を定義することも非常に役立ちました。 TextAppearanceとは、Androidが提供している文字色や文字サイズ、フォントスタイルをまとめた小さなstyleのことで、文字表示に関する設定を一括で適用することができます。 非常に便利なので、クックパッドアプリでもこれを真似して独自のTextAppearanceを定義し、提供するようにしました。 クックパッドアプリでは文字に使える色は7色あり、そこに文字サイズが5段階と文字の太さが標準/太字の2種類、全部で70種類の組み合わせがあります。 この70種類の組み合わせについて、CookpadFont.Base.ExtraSmall.Whiteのようにそれぞれの属性がわかるような名前で定義し、実際に利用するもののみCookpadFont.Base.*を継承したCookpadFont.*、といった形で定義しています。 わざわざ継承させているのは理由があり、CookpadFont.Base.*階層で名前による継承を利用しやすくなるほか、同じCookpadFont.Base.*を利用する複数のCookpadFont.*をわかりやすくする目的があります。

text_appearance_base.xml

<style name="CookpadFont.Base" parent="TextAppearance.AppCompat" />

<style name="CookpadFont.Base.ExtraSmall">
    <item name="android:textSize">@dimen/extraSmallTextSize</item>
</style>

<style name="CookpadFont.Base.ExtraSmall.White">
    <item name="android:textColor">@color/white</item>
</style>

text_appearance.xml

<style name="CookpadFont.CaptionWhite" parent="CookpadFont.Base.ExtraSmall.White" />

どう変わったか

上記のような様々な改修を加えた結果、クックパッドアプリではリソースのxml行数を2500行以上減らすことができました。 もともとそれだけ管理できずに肥大化していたということですが、改修後は新しいstyleやdimenを定義せずに既存のものを再利用するようになったおかげで新しい定義が増えることもほとんどなくなりました。 リソースの管理が適切にできるようになったと言えそうです。 今回の改修の中でもTextAppearanceの導入によって文字色・文字サイズの組み合わせに名前が与えられ、エンジニアやデザイナの共通言語として扱えるようになったのが特に大きな成果で、開発効率の低下を防ぐだけでなく向上させることができました。 リソースの整理は地道で根気のいる作業ですが、デザイン関連の開発効率の低下が気になっている方はぜひ検討してみては如何でしょうか。

TextAppearanceによるデザイン指示行われている様子 f:id:nein37:20160517114325p:plain

色や文字サイズにわかりやすい名前がついたのでTextAppearance自体の仕様変更も簡単です f:id:nein37:20160517114334p:plain

おわりに

本稿ではAndroidアプリの開発効率改善に関する最近の取り組みについてご紹介しました。 モバイル基盤グループでは引き続き最短距離でユーザーさんに価値を届けるための仕組みを考え、実施していきます。

弊社採用ページ(iOS/Android アプリエンジニア)

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/