Webサービス(Rails)+スマホアプリを作るときにやってはいけないこと 2018年版

なんとなく最近いくつかのWebサービスを触って「あ、こういう作りしちゃうとしんどいんだな・・・」というのが少しだけ分かってきたので、ほんとになんとなくメモっておく。

「下書き」「公開」をstatus=draft/publishedとかpublished=true/falseで表現してはいけない

class Article < ApplicationRecord
  belongs_to :user

  scope :draft, ->{ where(published: false) }
  scope :published, -> { where(published: true) }

  def publish!
    update!(published: true)
  end

こういうモデルを作ってしまうと

  • 下書きと公開後で「見ることができる人」が異なるべきなんだけど、ふとした拍子に Article.find(params[:id]) とか書いて( .published を付け忘れて)事故る
  • 下書きはそんなに厳密なバリデーションなく保存したいが、公開時にはちゃんとバリデーションしていてほしい。そうすると if: :published? unless: :published? がたくさん...
  • 公開時にプッシュ通知を送りたい、というのをやるにも「初めて公開状態になったタイミング」の取得が難しい

などなどの理由で「事故りやすい」コードになってしまう。

「下書き」は素直にモデルを分けよう

class ArticleDraft < ApplicationRecord
  befongs_to :user

  def publish!
    Article.create!(
      user_id: self.user_id,
      body: self.body
    )
  end
class Article < ApplicationRecord
  befongs_to :user

下書き状態をフラグではなくすことにより、

  • Article.find(params[id]) とかを不用意に呼んでしまっても、見えてはいけない下書きが混ざることはもうない。
  • バリデーションも独立で定義できる
  • 初めて公開状態になったタイミングはArticlewのcreateのタイミング。

のように、構造的に少し事故りにくくできる。

 

通知の処理はべた書きしない

class ArticlesController < ApplicationController

  def create
    if @article = current_user.articles.create(params.require(:article).require(:body))
      NotificationMailer.notify_article_created(@article)
    else
      render :new
    end

これは以前に書いたことそのものなんだけど、通知は結構いろんな種類があるのでコントローラにべた書きなんてやってしまうと

  • アプリ作るからプッシュ通知もやりたいな
  • アプリのAPIから通知したお知らせ一覧を見れるようにしたいな

みたいな要件があとづけでやってきたときに詰む。

通知は「きっかけとなったイベント」と「通知履歴」で表現する

yusukeiwaki.hatenablog.com

に書いたこと。

 

コア機能を「とりあえずWebViewで画面つくる」のは絶対にやめたほうがいい

Railsのコードで画面が作れるし、多少の仕様変更があってもアプリ側にアップデートしてもらわずに変更できるし、フォー最高!」みたいなかんじでWebViewが採用されることが稀によくある。

しかし、以下のような理由でWebViewを採用する場合には、REST API+ネイティブ実装が本当にダメなのかを再考すべき。

  • iOSAndroidの両方の画面をメンテするよりは、HTML+CSSでそれっぽい画面が作れる
  • 仕様が変わったときにアプリ側の変更無く反映ができる
  • Railsのコード書けるエンジニアはたくさんいるけど、アプリのコード書けるエンジニアは数人しかいないので、Railsのコードに近いほうがメンテ楽そう

WebViewは

  • 特定のページにJSを注入する
  • 特定のURLをブラウズしようとしたときに処理を横取りしてネイティブ側に処理を戻す
  • カスタムヘッダーをつける

など、ブラウザでは簡単にできないことが自由にできる。

その半面、これらを使ってしまうとブラウザでコンテンツ試験困難になり、iOSAndroidのアプリ両方と結合動作確認しないといけない。そうすると気づく。「まったくラクできてないぞ?!」と。

WebViewじゃなくて素直にブラウザを使ったほうがいい

WebViewは基本的に使わないほうがラクだが、

  • 認証が不要
  • Web側で機能が完結している(○○のリンクを踏んだときはネイティブ側の画面を表示する、みたいなのが無い)
  • ユーザがブラウザに保存している個人情報(パスワードとか氏名・住所とか)の補完やクッキー情報を利用したい

のような場合には、ネイティブじゃなくてWebViewを採用したいなというのはアリ。ただ、それならWebViewではなく素直にブラウザを使ってWebページを表示したほうがいい。

Yahooの認証画面とかがいい例で、認証時はブラウザで専用の認証ページに飛ばされて、認証が終わったらカスタムスキームのURLでアプリに戻ってくる、という作りをしている。

素直にブラウザで表示すると何がいいかというと

  • ダサい
    • コア機能をWebViewで済ませちゃおうという気が失せる
  • 試験がしやすい

が大きな理由。「コア機能は自然とネイティブ化したくなる」ってのは本当に大きな理由。

WebViewは意外とメンテが大変なので、REST API+ネイティブ実装したほうが結果的にはラクなことが多い

WebViewは、実運用においては

①思ったほど更新されず →②メンテされず →③いつの間にか壊れて →④意図したとおりの使われ方がされず →⑤結局ネイティブに置き換えられる or 単純に捨てられる

みたいなライフサイクルをたどることが多い。

REST APIであればrequest specで自動試験しやすく、どこかの誰かがモデル構成を変えて壊れそうになっても事前検知ができる。しかしWebViewのように画面を返すものはRailsでも試験を書くのが格段に面倒だ。雑に試験を書かないでいると、どこかでモデル構成がいつの間にか変わっててWebViewだけいつの間にか壊れる、みたいな状態になる。

また、そもそもWebViewで作る画面は、Railsのコード書く人にとっては「アプリの画面」として敬遠されがちだし、アプリのコード書く人にとっては「CSSとかちゃんとわかってないといじれない画面」として敬遠される。

ヘルプページとか、OSSライセンス表示みたいに、見る人/見るシーンが限られている機能であればWebでいいのだけど、少なからずユーザに使ってほしいコア機能であれば、最初からネイティブ実装したほうが、ちゃんとメンテもされるし継続的に利用されるだろう。

 

「一度しか表示しない」ものは極力なくそう

個人的にはなんで存在しているのか全くわからないんだけど、ウォークスルーみたいな機能って割とたくさんのアプリで実装されている。

ウォークスルーは

  • 「読まない取説を無理やり読まされてる」ものなので、大抵の人は読まずにスキップする
  • 最初の1回しか表示されないので、ユーザの印象には全く残らない
  • 画面のスクリーンショットが貼られていたりすると、画面が変わるたびに画像差し替えが必要になって、デザイナもエンジニアも対応が必要

という、この上なくコスパが悪い機能だ。

説明のためのウォークスルーを実装するくらいなら、コア機能を説明なしで使えるようにUI改善したほうがいい

「説明をしないと使ってもらえない」という不安をウォークスルーとか説明のポップアップみたいなもので払拭するのがそもそもの間違いで、説明しなくてもいい状態を作り出すことがユーザにとっても開発者にとっても良い状態だ。

もっというと、「説明を必要とする機能であれば作らないほうがいい」という判断もアリだ。説明が必要というのは「ユーザが必要としていないものを作っている」ということなのかもしれないと疑ったほうが、シンプルに仕上がる。

ユーザが求めているのは紙芝居のウォークスルーではなく、セットアップウィザード

iPhone買ったときだって、最初の1回だけ表示される機能があるじゃない?」

そう、あれはウォークスルーではなく、セットアップウィザードだ。「セットアップしないとちゃんと使えないので、一緒にセットアップして行きましょうね!」という目的のセットアップウィザードは、ウォークスルーとは似て非なるもの。

Wantedly Syncが昔、シンクマっていうキャラクターとのチャットを通じて使い方を説明していく面白い仕組みをつくっていた。

会話履歴のない人がアプリを立ち上げると、いきなりボットに話しかけられて

f:id:YusukeIwaki:20180817101847p:plain

他のメンバーの招待の仕方やメンションの仕方を教わる、というもの。

f:id:YusukeIwaki:20180817101833p:plain

こういうインタラクティブにセットアップ/使い方の習得をしていくのが、おそらくユーザが求めているものだ。

 

 

まとめ

複雑なものは、メンテされずに忘れられて壊れて、置き換えられるか捨てられる。

シンプルにいこう。

長生きしたいなら、パンは自分で焼くのがいいかも

久々に、全くエンジニアリング関係ないネタです。

最近、パンを焼くのにハマっている。食パンを買ってきて焼くんじゃなくて、小麦粉にいろいろ混ぜてコネコネして焼くほうのパン焼き。

パンを焼いてて考えたことをなんとなく書いてみた。

まずは、オレオレパンの焼き方を紹介するぞ

はじめは面倒と思ってたパン焼きが、意外とお手軽カンタンだったので紹介しておきたい。

を適当な小皿にいれて放置。 →材料①

  • 小麦粉(全粒粉) 240g
  • 水160g
  • 豆乳40g
  • 食塩3g
  • メイプルシロップ少し(目分量で大さじ1くらい)

3合炊きの炊飯釜 にいれてまぜまぜ。(はかりに釜を載せて材料を量りながら入れていくと、洗い物が出なくて超ラク) →材料②

材料①と材料②を混ぜれば、もうパンの香りがほのかにしてくる。

f:id:YusukeIwaki:20180616200400p:plain

炊飯器をセットして、発酵のために保温1時間。

1時間たったら、さらに少しまぜまぜして、炊飯。

すると・・・

f:id:YusukeIwaki:20180616200521p:plain

多少不格好だけど、まぁまぁそこそこ、というかかなり美味しいパンが出来上がる。

 

ちなみに、↑の手順をみて「まぁまぁめんどくさいじゃん?」と思った人がいるかも知れないけど、多分実際やってみたらそんなことはないとわかるはずだ。

そもそも私はものすごく面倒くさがりで飽き性だ。ちまたの料理番組とか見てると、次々にボウルに分けられた材料を投入していくシーンを見て「その大量のボウル、洗うの大変だよね!」とか思ってしまって、たいていの料理は参考にはできない。そのくらい面倒くさがりなのだ私は。

それでも、↑のパンづくりは続けられてる。だって、パンつくったあとに 洗い物がほとんどなくて 、美味しいパンが食べられるんだぜ?最高だぜ?

 

(これは完全に余談なんだけど、ホームベーカリーはダメだ。ホームベーカリーは洗うの大変だし、パン焼く以外には用途なくて場所取るから、おいしいパンを食べるのに見合った工数ではパンは焼けない。↑のオレオレパンは炊飯器で焼くというのが大きなポイントだ。)  

しかしこのパン、材料費がかかるぞ?

オレオレパン、おいしさとお手軽さは素晴らしいのだけど、実はなかなかに材料費が高い。

  • ドライイースト・・・28円(10袋で280円前後)
  • 小麦粉・・・約288円(500gで600円前後)
  • 水・・・10円(2リットルで100円の、サントリーのなんちゃら天然水)
  • 豆乳・・・8円(1リットルで200円前後)
  • 食塩・・・1円未満
  • メイプルシロップ・・・20円(300gで800円前後)

→355円!! 食パン1斤でだよ??

高い原因は間違いなく

こいつだ。有機の全粒粉 を使ってるからだ。

実のところ、安い小麦粉 を使えば、1kgで350円くらいなので、計150円くらいのパンになる。

ただ、安い小麦粉だと全然美味しくないパンが出来上がってしまったため、モチベーション維持のために有機全粒粉を使うことにしたのだ。

 

ところで、市販のパン・・・安すぎないか?

オレオレパンは、小麦粉で少し贅沢してるのは間違いない。小麦粉以外でも、メイプルシロップとかで少し贅沢してるのかもしれない。

でもちょっとまて、本当に贅沢なのか、これ??

逆に、スーパーとかコンビニで1斤100円とかの食パンって、一体どーやって作ってるの????大量仕入れだけで、本当にそんな安く作れるか?!

ということで、とりあえずファミマで100円で売ってるパンの裏をみてみよう。

f:id:YusukeIwaki:20180616211417p:plain

小麦粉、パン酵母、食塩、はわかる。

 

ショートニング ??((((;゚Д゚))))ガクブル

 

乳などを主要原料とする食品 ???((((;゚Д゚))))ガクガクブルブル

 

 

なにこれ・・・!?

 

 

てきとうに予想するに、

  • バターは高いからコストカットのためにショートニング使って、
  • それだと美味しくないから果糖ブドウ糖液糖で甘みでごまかして、
  • あとは味をととのえるために色々いれて、

みたいな、健康への配慮は二の次にした企業努力のようなものはあるのかもしれない。

それにしても、100円で売れるパンは安すぎる。これ、小麦粉は一体どんな粗悪品を使っているのだ・・・?!

 

食の安全を考えると、安いものは実はそんなに安くない?

100円のマクドと400円のモスはどっちが安いか

(※ これは完全に空想話なので全く根拠がある内容ではない)

たとえばハンバーガーで、マクドは100円。モスは400円。

週1くらいの頻度で食べて、仮に

  • マクド食べてた人は10年後に病気を発症して、その治療に40万くらいかかる
  • モス食べてた人は20年後に病気を発症して、   〃

としたら・・・?

1年は約52週間なので、1回あたりの治療コストの積立は

マクド→766円 モス→383円

くらいになる。

ハンバーガーの値段と足し合わせると、マクドが866円、モスは783円。 モスのほうが1割ほど安くなる。

あらためて、オレオレパンは高いのか?

パン好きな人であれば、食パンはハンバーガーよりも高頻度で食べるはず。 毎日2枚食べるとして3日でなくなる計算。

ちまたの100円食パンをX年食べ続けて、仮にその治療費に40万かかるとすると、そのときの治療コストの積立は

X 治療コスト積立
5 657
10 328
15 219

ということで、約13年以上長く病気にならずに食べ続けることができれば、オレオレパンよりも100円食パンのほうが真に安いということになる。

 

私の場合は、体質的にダメなのか、ショートニングを摂取した途端に皮膚が荒れ始める。なので、13年摂取しつづけて病気にならないとか無理だww

ということで、健康維持のコストも考えて、これはやっぱりパンは自分で焼いて食べるぞ! ってなった。

 

まとめ

  • 健康維持のためのコストは意外と高い
  • オレオレパンはとても美味しい

"else if"を書くよりも先に...

まえおき

最近、仕事をしていて「つまらない、もうやめたい」って思ったときってどんなときだったっけ?と振り返ってみた。

モチベーションが下がりまくりな仕事の一因として、「パターンが多い」ってのがあると思った。

function handleWebHook(event) {
  if (event.type == "Liked") {
    if (event.source == "Article") {
      handleArticleLiked(event.data);
    }
  } else if (event.type == "Comment") {
    if (typeof event.data["bot_id"] != "undefined") {
      handleBotMessage(event.data);
    }
  } else if ( ... ) {
    ...

重厚長大なif文は、テスト書くときにも死ぬほどパターンを用意しないといけなくて、とにかくストレスがたまる。

こんな感じのコードが300行くらい続いたら、確実に集中力が切れる。てかキレる。

"else if" よりも、クラス分割?

であれば、分割すればいいやん?というのは自然な発想。

ここでいきなりPythonっぽいサンプルコードになるけど、

class LikeHandler(BaseHandler):
  def shouldHandle(event):
    return event.type == "Liked"

  def handleEvent(event):
    if event.source == "Article":
      handleArticle(event.data)


class CommentHandler(BaseHandler):
  def shouldHandle(event):
    return event.type == "Comment"

  def handleEvent(event):
    if "bot_id" in event.data:
      handleBotMessage(event.data)

...

eventHandlers = [
  LikeHandler,
  CommentHandler,
   ...
];

def handleWebHook(event):
  for handler in eventHandlers:
    if handler.shouldHandle(event):
      handler.handleEvent(event)

Pythonに限った話ではなくて、JavaでもRubyでもだいたいこんな感じで分割するのはぱっと思いつく。分割することで、テストもクラスに応じて1つずつ書いていけるし、ストレス値は一気に減る。

でも・・・

  • 毎度 def shouldHandle とか event.type== とか def handleEvent とか書かないといけないのってだるいよね
  • def handleEvent って結局どういう処理するのかって、ソースのコメント書かないと、メソッド名ではぱっと読めないよね
  • 多くのhandlerは event.typeで判定するんだろうけど、ほんとにそうなの?ってのが全部のhandlerみないとわからない

というところが今度は地味なモチベーション低下要因になってくる。

より "else if" の「判断ポイント」を明確にする書き方

Pythonbottle とかFlask っていうAPIサーバを書くライブラリをみて、ふと思った。

かりに

router = Router()

@router.on('Liked')
def handleLike(event):
  likeRouter.route(event.source, event)

@router.on("Comment"):
def handleComment(event):
  if "bot_id" in event.data:
    ...

 (中略)

def handleWebHook(event):
  router.route(event.type, event)

みたいに書くことができたらどうなるだろう?と。

毎回 event.type== xxx とか should_handle(event): return xxx とかを書く手間もなければ、判断基準(event.typeで判定しているという事実)がブレることもない。

直感的に「これだ!」とおもったので、Pythonで適当にライブラリを作ってみた。

github.com

Pythonじゃなかったらどう書く?

JSならこんな感じ?

class Route {
  constructor() {
    this.routes = {}
  }

  on(key, target) {
    this[key] = target;
  }

  route(key, ...args) {
    if (typeof this[key] == 'function') {
      this[key](...args);
    }
  }
}

r = new Route()

r.on("Liked", (event) => {
  ...
})

r.on("Comment", (event) => {
  ...
})


///////////

r.route(e.type, e);

結論?

else if を30個くらい書いてて吐き気がするところは、積極的に分割しよう。

Routerっぽいものを実装すると見通しが良くなるかもしれない。