RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

デザインセンスとは?


どうも!お久しぶりです!デザイナーの渡辺です!
リモート勤務になって早2ヶ月になりました。
毎日ストレッチをしていたら、だいぶ体が柔らかくなってきました(*´∀`)

さて、今回は【デザインセンス】についてお話していこうと思います。
特に自分のことを【デザインセンスがない】と思っている方に見ていってほしいです!

最初に感じた疑問

私はデザイナーになってから、デザインセンスって一体なんだろう?と
考えるタイミングが何度かありました。

最初は新卒の時です。

Aさん:この企画のバナー作って!俺はデザインセンスないから見た目は任せる!
私:(私にデザインセンスってあるのか?)

単純にデザイナーだからデザインセンスを持っていると
思われていることに疑問を感じました。

私:私ってデザインセンスあるんですか?

先輩:ないだろ
私:わーバッサリー(棒)

そりゃもうバッサリでした笑

その後も事あるごとに、デザイナーではない方から
「自分にはデザインセンスがないのであとよろしく!」と言われることが多々ありました。
これはデザイナーのあるあるなんじゃないですかね?笑

デザインに必要なもの

そもそもデザインするのに何が必要なのか?
正直、なにもいらないと思ってます。
デザインしたければ、好きなようにデザインすればいいのです!

ただ、そのデザインを
【多くの人が良いと感じる】ものにするには

【知識】と【経験】
この2つが必要だと思ってます。

【知識】

  • 配色:色によって与える影響はなにか?
  • 配置:コンテンツの置く場所によって与える影響はなにか?
  • ボタンは押しやすいか?欲しい情報は得られるか?などを考えられるUIの概念
  • 与えたものによってどのような体験をするか、してほしいかを考えられるUXの概念

【経験】

  • 数多くの物を作ったり、目にしたことがあるか
  • 作ったものが成功したか
  • 作ったものが失敗したか
  • 失敗から成功に繋げられたか

など、挙げたらまだまだありますが
このような【知識】と【経験】を蓄積することにより
【多くの人が良いと感じる】ものを
作り出すことが、出来るのではないかと思います。

デザインセンスとは

では、デザインセンスとはなんなのか?

それは【知識】と【経験】を蓄積し、磨き上げた
自身のデザインスキルのことを言うのではないでしょうか?

蓄積することなく始めから持っているとすれば
それは才能です。

センスがあると才能があるは別物です。


【デザインセンスがない】というのは
デザインの【知識】と【経験】がないと言う意味なので、当たり前ですよね。
むしろ、初めからデザインセンスがある人のほうが希少です。
【知識】と【経験】がなくできてしまうので、才能があります。

新卒だった頃の先輩も
まだ新卒で【知識】と【経験】がないんだから
デザインセンスがあるわけない。
と言う意味だったようです。(いや、教えてよ)

そうと分かれば簡単です。

知識と経験は誰にでも蓄積すればいいのです笑

デザインセンスは磨くものです。

特にデザインに関しては、
始めからデザインセンスを備えていないとできないものと
思われがちですが、そんなことはないです。

デザイナーの私にだって、デザインセンスはなかったのですから笑
勉強したか、それを数多くこなしたかの違いなだけなんです。
なにも特別ではありません!誰でもできます!


なので、デザイナーではない方も試しにデザインしてみてください。
何でもいいです。
以前書いた記事を参考に、資料を作ってみるのはどうでしょうか?

rarejob-tech-dept.hatenablog.com

少しデザインの知識があるだけで
受け手の印象が変わってきます。

その先の成功に繋がるかもしれません٩(๑`^´๑)۶

まとめ

デザインセンスについて話してきましたが
今回のお話は【デザインセンス】だけでなく
【センス】という言葉のつくもの全てに言えることだと思います。

自分が○○○のセンスが無いから、、、と諦めてしまっていることがないか
改めて見直してみても良いかもしれません。

それはきっと磨けるスキルなので
【知識】と【経験】を蓄積することで、 今までは諦めていた新しい趣味が見つかるかもしれません!

もちろん、今回のお話は人によって捉え方が違うと思います。
自分なりに解釈し、デザインに触れていただけると嬉しいです(^^♪

それではよい週末を ノシ

身近なデータ分析 〜クラスの継承関係を題材に〜

@hayata-yamamotoです。この記事を書いていたら、小学校の自由研究を思い出しました。当時私は、アリジゴクの採集にハマっていて、研究テーマにしたことがありました。成虫であるウスバカゲロウになるとあっという間に生涯を終えてしまう儚さはなんとも言いがたいものがありますね。

さて、私はEdTech Labという研究開発チームに所属しています。(前回の記事に登場した齋藤と同じです)普段は、Python機械学習のモデル開発やデータ分析をしています。以前、分析チームについては書きましたので、よければそちらもみてください。

rarejob-tech-dept.hatenablog.com rarejob-tech-dept.hatenablog.com

目次

はじめに 

多くのプログラミング言語では、別のクラスをあるクラスに継承させることができます。これにより、共通部分の実装を省きながらユースケースに合わせた追加実装ができるようになります。また、共通部分も上書きすれば、必要な実装に書き換えたりできます。例えば、Djangoでは、以下のようにクラスを継承し必要な実装を加えて使ったりします。

# 引用元: https://docs.djangoproject.com/en/3.0/topics/class-based-views/

from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book

class BookListView(ListView):
    model = Book

    def head(self, *args, **kwargs):
        last_book = self.get_queryset().latest('publication_date')
        response = HttpResponse()
        # RFC 1123 date format
        response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
        return response

本記事では、この継承に着目します。具体的には、継承を「あるクラス」→「別のクラス」という関係性で捉え、ネットワーク分析の枠組みで分析します。最初にUMLで描いたクラス図を定性的に分析します。次にネットワーク分析の指標を用いて定量的に分析しました。普段何気なく実装しているクラスの継承関係も数学的に捉えなおせば、より客観的で俯瞰的な理解ができると伝われば嬉しいです。

なお、ソースはGitHubに掲載してあります。

github.com

使ったデータ

collections.abc1というPythonの標準モジュールを対象に分析しました。データの選定理由は大きく2つあります。念のためですが、このモジュールを知らなくても問題ないですので安心してください。

  • よく使われるデータセットはもうすでに良い記事がたくさんあるから
  • このモジュールを深く理解したかったから

データ作成に当たっては、公式のドキュメントとcpython2を参考にしています。コレクション抽象基底クラスの継承関係と抽象メソッド、及びMixinについて記載したテーブルが公式には記載されています。このモジュールは、typing3の説明にもしばしば登場します。mypy4などを使用するに当たっては、知っておいて損はありません。

また、分析にあたってCSVデータを別途作成しました。(気を利かせて?)Gremlin形式5になっています。もし興味があれば、Gremlin形式でロードできるグラフDBと合わせて使ってみてください。例えば、AWS Neptuneとかでしょうかね。

$ head -n 5 data/vetices.csv
~id,name:String
v0,"Container"
v1,"Hashable"
v2,"Iterable"
v3,"Iterator"

$ head -n 5 data/edges.csv
~id,~from,~to,~label
e0,v3,v2,extends
e1,v4,v2,extends
e2,v5,v3,extends
e3,v8,v6,extends

ネットワーク、そしてグラフ

普段、ネットワークという言葉をよく耳にします。SNSやインターネット、VPN、サブネットとかネットワークACLなどなど、身近なものからテクニカルなものまで様々あります。ビジネスの文脈ですと、ネットワークというのは人脈のような人同士の繋がりを意味することもあります。要するに、あるものと、別のあるものがつながっている時に、「ネットワーク」という言葉を使いたくなります。このような構造を数学的に扱うのに、グラフが有効です。

グラフと言っても、棒グラフなどの図を意味するわけではありません。宮崎氏によると、『いくつかの点と、二つの点を繋ぐ線からなるもの』6と説明されています。この点を頂点(Vertex)や節点(Node)と呼び、繋ぐ線を辺(Edge)や弧(Arc)と呼びます。また、この辺に向きがあるものを有向グラフ(Directed Graph)、向きがないものを無向グラフ(Undirected Graph)と呼びます。実務でよく目にするグラフといえば、木(Tree)や有向非巡回グラフ(DAG)あたりでしょうか。隣接や(最短)経路なども基礎的で重要ですが、今回は省略します。

以下のリンクは、レ・ミゼラブルに登場するキャラクター相関図をグラフで表現しています。目まぐるしくすぎていくストーリーも、一度立ち止まって、それぞれの登場人物を点と線で結んでみるとより深く理解できるというわけです。まさに、"Connecting the dots"ですね。エモい。

observablehq.com

まずはUML

とはいえ最初からグラフを使おうとすると、難しすぎて心が折れてしまいそうです。ですので、まずはこのモジュールをUMLでクラス図にしてみるところから始めます。ちなみに、UMLは以下の記事でも紹介されています。

rarejob-tech-dept.hatenablog.com

この分析では、大きく二点を確認しました。

  • モジュール全体を把握し、クラスを覚える
  • どのクラスがたくさん継承していそうか、継承されていそうかを直感的に理解する

以下の図を確認すると、直感的には

  • Collection, Set, MappingViewに向いている矢印が多い
  • Collectionは出ていく矢印も多い
  • いくつかエッジを持たないクラスが存在する

とわかるのではないでしょうか。

f:id:hayata-yamamoto:20200525220140p:plain
collections.abc Diagram

いよいよ分析

次に定量的な分析をしてみます。今回の分析には、NetworkX7というライブラリを用いました。分析には以下の3つを用います。

  • 次数中心性 (degree centrality)
  • 媒介中心性 (betweenness centrality)
  • PageRank

中心性とは、「そのノードがネットワークの中でどれくらい重要か」を表す指標と思ってくれれば大丈夫です。人で例えると、次数中心性は「たくさんの人とつながりを持っていること」を評価し、媒介中心性は「その人経由で別の人と繋がることが多いこと」を評価しています。PageRankは、Webページの重要度をはかる指標で被リンクの数を重視します。(一部の例外を除けば)良い記事は、被リンク数が多くなることを期待できます。PageRankはその性質を数値的に表現したものくらいの理解で大丈夫です。色々なページで丁寧に説明されていますので、検索してみると理解が深まるかもしれません。勇気がある人はぜひ、論文8も読んでみてください。

さて、CSVを読み出して有向グラフを作成し、そのグラフに対して必要な計算を実行します。JupyterNotebookの処理を抜粋してみます。

import networkx as nx
import pandas as pd
from typing import Tuple

def pairing(row: pd.Series) -> Tuple[str, str]:
    return (row['~from'], row['~to'])

vertices = pd.read_csv('./data/vertices.csv', index_col=None)
edges_df = pd.read_csv("./data/edges.csv", index_col=None)
edges_df['pair'] = edges_df.apply(pairing, axis=1)

G = nx.DiGraph()
G.add_nodes_from(vertices['~id'])
G.add_edges_from(edges_df.pair)

degree = nx.degree_centrality(G)
between = nx.betweenness_centrality(G)
page_rank = nx.algorithms.pagerank_numpy(G)
df = pd.DataFrame.from_records([degree, between, page_rank], index=['degree', 'betweenness', 'pagerank']).T
df = pd.merge(left=df, right=vertices, left_index=True, right_on='~id').drop('~id', axis=1).set_index('name:String')

このDataFrameをheadすると以下のようなテーブルが取得できます。

name:String degree betweenness pagerank
Container 0.0416667 0 0.0536147
Hashable 0 0 0.019103
Iterable 0.125 0 0.118524
Iterator 0.0833333 0.00181159 0.0353406
Reversible 0.0833333 0.00271739 0.0410238

(よしなに)可視化すると以下のようになります。ノードの大きさは、各指標の値の大きさと比例しています。それぞれの指標によって評価のされ方が異なることがみて取れますね。 f:id:hayata-yamamoto:20200528233423j:plain

最後に、各指標毎にランキングをとって、その合計順位の高い順上位5つを並べると以下のようになります。UMLでの定性分析通り、CollectionやSet, MappingViewが上位にあると確認できました。めでたしめでたし

name:String Sum of rank value
Collection 3
Set 8
Sequence 12.5
MappingView 16.5
Mapping 23.5

おわりに

この記事では、継承をテーマに簡単なネットワーク分析を行いました。ところどころ難しい言葉や解釈に苦労する部分もあったかもしれませんが、普段の何気ないテーマも数学的に捉えなおせば、よりよく理解できることが少しわかっていただけたら嬉しいです。実はそんなに頑張ってテーマを探さなくても、意外と身近に分析のテーマは落ちていて、そこまで難しい手法や実装をしなくても解決できたり調査できたりします。

ネットワーク分析は適用範囲が広く、今回取り上げたテーマの他にもSNSや書籍引用、Webページなど手頃なテーマはたくさんあります。ぜひ手頃なテーマを見つけて勉強してみてください。

引用・参考

時系列予測ライブラリProphet触ってみた

こんにちはEdTech Labの齋藤です。初めての投稿です。 緊急事態宣言が解除されつつありますが、生活リズムが在宅勤務に最適化されてしまったため、出勤が再開された際、寝坊に苦しむこととなりそうで心配な今日この頃です。

さて、今回は最近少し触れる機会のあったProphetというライブラリがとてもよかったので紹介しようと思います。

Prophetとは

facebook社が提供している時系列予測ライブラリで、RおよびpythonAPIが提供されています。 簡単な実装で時系列予測ができるので、さっと時系列予測をしたいときとてもうれしいライブラリです。

実装

実際どれくらい簡単なのか、ProphetのQuiq Startを参考にpythonで実装してみます。 Prophetはfbprophet==0.6を使用しています。

では、データを読み込んで準備をしましょう。

import pandas as pd
from fbprophet import Prophet

df = pd.read_csv('examples.csv')

使用するデータセットはQuiq Startと同じものを使用します。 自前のデータセットを利用する際に注意すべき点として、Quiq Startのようなナイーブな実装では、非連続なデータや1日を超える単位のものは使用しないようにするという点です。これを守らないと、予測がうまくいかないという状況が発生します。 また、それらの対応策はNon-Daily Dataの項目で言及されていますが、今回は割愛します。

では、モデルを作ってみましょう。

m = Prophet() # インスタンス化して
m.fit(df) # データをfittingさせる

これで、先ほどのデータをモデルに入れることができました。 では、予測結果を返してみましょう。

future = m.make_future_dataframe(periods=365) # periodsでどれくらいの期間予測するか決める

forecast = m.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()

これで完了です。 実質7行のコードで時系列予測ができる、とてもうれしい...。

最後に

Quick Startではデフォルトの設定でとてもナイーブな実装となりました。 そのほかにも周期性やトレンドの追加、各種パラメータの設定などリッチな仕様となっていて、ここまで簡単かつ自由に時系列モデリングができるということに感動しました。 自分だけの最強の時系列予測モデルを作っていきましょう。

参考: facebook.github.io

CloudFront + S3 でオリジンが更新されたら自動でキャッシュ削除する仕組みを作る

在宅勤務で引きこもりの才能に目覚めた DevOps チームの Shino です。

美味しいラーメンを食べたい衝動が時々訪れるのが悩みです。 おすすめの取り寄せラーメンがありましたら教えて頂きたく。(ジャンル問わず)

今回は CloudFront + S3 の構成で S3 上のコンテンツが更新されたら自動で CloudFront 上のキャッシュを削除する仕組みを作ったので簡単にまとめます。

概要

状況

CloudFront + S3 の構成で静的 Web ページなどの配信を行っている。

CloudFront が S3 上のコンテンツをキャッシュしてくれるので配信自体のパフォーマンスは悪くない。

課題

S3 上のコンテンツを更新するたびに、手動で CloudFront 上のキャッシュを削除しているため手間がかかる。

∵ キャッシュされたままの古いコンテンツが配信されてしまうのを防ぐため

解決策

Lambda を用いて S3 上のコンテンツが更新されたら自動で CloudFront 上のキャッシュを削除する。

f:id:shino8383:20200507001849p:plain

やったこと

全体の流れ

  1. S3 のオブジェクトが更新・作成されると Lambda にバケット名・オブジェクトのパスの情報を含んだイベントが渡される
  2. Lambda 上で、渡された S3 バケットをオリジンとする CloudFront ディストリビューションを取得する
  3. 取得した CloudFront ディストリビューションに対して、渡されたオブジェクトのパスを元にキャッシュ削除を実行する

実装内容

■ S3 - Lambda 間連携

1.S3 のオブジェクトが更新・作成されると Lambda にバケット名・オブジェクトのパスの情報を含んだイベントが渡される

これは Lambda のトリガーに S3 を設定すれば自動でイベント通知してくれます。 すべての更新イベントに対して設定しておいて問題ないかと思います。

f:id:shino8383:20200507024817p:plain

■ Lambda の実装

叩く API は実装する言語を問わず共通のはずなので、API リファレンスを記載しながら説明します。

2.Lambda 上で、渡された S3 バケットをオリジンとする CloudFront ディストリビューションを取得する

これは CloudFront API

ListDistributions - Amazon CloudFront

を使ってディストリビューションの一覧を取得します。

ただ、ListDistributions にはフィルターの機能はないので目的のオリジンを持つディストリビューションかどうかの判定は自前で条件分岐を作るしかないかと思います。

次に、

3.取得した CloudFront ディストリビューションに対して、渡されたオブジェクトのパスを元にキャッシュ削除を実行する

これは CloudFront API

CreateInvalidation - Amazon CloudFront

を用いてキャッシュ削除を実行します。 引数でディストリビューションの指定とオブジェクトのパスの指定を行います。

実装上の注意点

今回、実装するにあたって注意した点が3つあります。

  • Lambda 関数の冪等性
  • API のリクエスト数制限
  • Lambda の利用料

■Lambda 関数の冪等性

Lambda 関数の冪等性については調べれば色々情報が出てくると思いますので詳しくは触れません。 簡単に説明しますと

"実行した回数に関わらず同じ結果が得られる性質"

のことを冪等性といいます。

そして、

Lambda は複数回実行される可能性がある

という仕様があるので冪等性を考慮しています。 これは今回のケースで言えば S3 上のオブジェクトを 1 回更新しても、Lambda は 1 回以上実行される可能性があるということです。

今回実装したキャッシュ自動削除では、

オブジェクトの更新 1 回に対して、キャッシュ削除が 1 回だけ実行される

という結果が欲しかったので、キャッシュ削除の API を叩く前に 実行中のキャッシュ削除がないかを判定するロジックを設けました。 この判定がなければ、同じオブジェクトに対する複数のキャッシュ削除が重複することになります。

キャッシュ削除の一覧は

ListInvalidations - Amazon CloudFront

で取得しました。

f:id:shino8383:20200507041107p:plain

API のリクエスト数制限

AWSAPI には一定時間当たりのリクエスト上限があります。 一定時間内にリクエスト上限を超えるとエラーが返却されます。

例えば SNSAPI の一つ、 Subscribe は 1 秒あたり 100 回の制限があります。

Subscribe - Amazon Simple Notification Service

This action is throttled at 100 transactions per second (TPS).

CloudFront の API リファレンスには回数の記載がないですが、制限自体はあるはずです。

実装の際にリクエスト回数が増えすぎないかテストしていましたが、ThrottlingException が何度か返ってきていた記憶があります。

Common Errors - Amazon CloudFront

つまり今回のケースでは、オブジェクト更新が一度に大量に行われる場合などを考慮して必要以上に API をコールしないような実装が必要です。

冪等性の件でキャッシュ削除を行わない分岐を設けたのは、影響は軽微ですがリクエスト数の観点からも良いと言えるのではないかと思います。

■Lambda の利用料

最後にお金の話です。

最低でも更新されたオブジェクトの数だけ Lambda が実行されるので Lambda の利用料にも気をつかうことにします。

Lambda の利用料は

利用料 = 実行回数 * 割り当てたメモリの量

で決まります。

Lambda 上のロジックでは実行回数(=更新するオブジェクトの数)のコントロールはできないので、割り当てるメモリの量が小さくなるように配慮します。

具体的に言えば、一度に大量にデータを保持する処理は避けることにします。

今回の実装の中で、データを大量に取得しやすいのが

ListInvalidations - Amazon CloudFront

だと思ったのでこちらの処理に対して手を加えることにします。引数で何も指定しなければ指定したディストリビューションについて過去のキャッシュ削除履歴を相当な件数引っ張ってくることになるかと思います。 (15000 件近く返却されるところまでは確認しました)

今回は、弊社の CloudFront + S3 の利用状況も考慮した上で

  • 取得するキャッシュ削除履歴の数を引数で指定
  • キャッシュ削除の履歴を新しいものから 10 件取得する。
  • 実行中の履歴があればもう 10 件取得する。
  • これを最大で直近 30 分以内の履歴について行う。

という実装をしました。

この実装はリクエスト数を増やしている側面もあるので、リクエスト数の増加とデータ取得量最適化の天秤になるかと思います。

最後に

CloudFront + S3 の構成はよくある構成だと思いますので、実装の際に考えていたことを改めて言語化してみました。

この Lambda (とその中で叩かれる API )は大量に実行される可能性があるので、 キャッシュ自動削除の対象にする S3 バケットやオブジェクトのパスはちゃんと考察するべきかと思います。

ちなみに今回は Slack にキャッシュ削除の開始通知を飛ばすところまで実装しました。

f:id:shino8383:20200514192553p:plain

これからもすきあらば自動化等で日々の業務のコストを下げていけたらいいなと思っています。

では今回はこのへんで。

DevOps チームは仲間を募集中です👏

👏採用/求人情報 | アピール | 未来の教育を作る人のマガジン インフラエンジニア(AWS)

UIWebViewからWKWebViewへのリプレイス作業が完了したのでまとめてみた

APP / UX チームで iOS アプリを担当しています玉置(@tamappe)です。

今回はレアジョブアプリでリリース当初から使われていた UIWebView を全て WKWebView にリプレイスできましたので、その時に取り組んだ事を知見としてまとめることにしました。 iOS アプリ開発において UIWebView の撲滅作業は一つの鬼門ですね。

リプレイスの背景

UIWebView とは Apple が提供するアプリ内ウェブブラウザの機能を持つ UIKit です。 簡単にいえば、アプリ内ウェブビューを実現するものになります。

ですが Apple が去年リリースした iOS13 の誕生とともに、正式に2020年4月からほぼ使えなくなることになります。 UIWebView が使えなく代わりに iOS8 から追加された WKWebView を使うようにと強制されるようになりました。新規アプリに至っては2020年4月以降から UIWebView を使ったアプリの申請はリジェクトが確定しています。既存アプリに関しても1度警告が入るだけですが、2020年12月末以降からはアップデートの申請が出せなくなります。

Updating Apps that Use Web Views

レアジョブアプリも例に漏れず UIWebView を使っている画面がありますので、今回を機に WKWebView へのリプレイスを実施することにしました。

注意すべきこと

では、 WKWebView へのリプレイス作業に関してやるべきことと注意すべきことをを箇条書きにします。

  1. UIWebView のインスタンスを WKWebView のインスタンスに置き換える
  2. WKWebView でのページのローディングで WKNavigationDelegate を使うようにする
  3. アプリ側で JavaScript 実行(同期)をしている箇所は、 WKWebView の場合は非同期(コールバック)になる
  4. ネイティブ画面でログインAPIを叩いている場合、 Cookie の共有の扱いに注意する
  5. UIWebView で UserAgent の操作をしている場合には、取得の仕方を考え直す

以上、5点になりました。

それでは一つずつ見ていきます。

UIWebView のインスタンスを WKWebView のインスタンスに置き換える

ほとんどの場合は UIWebView は storyboard から使って操作していたと思います。それに対して、 WKWebView は Xcode 10 まで Apple のバグにより storyboard から参照できませんでした。そのため、Xcode 10 まではコード上で WKWebView のインスタンスを生成するしかありませんでした。それが Xcode 11 からは Apple がこのバグを直したためか storyboard 上で使ってもビルドエラーが起こらなくなりました。

そのため、「だいたいの場合」は storyboard から WKWebView を使えば置き換え可能になりました。

UIWebView

f:id:qed805:20200223190550p:plain
UIWebViewの場合

WKWebView

f:id:qed805:20200223190654p:plain
WKWebViewの場合

WKWebView の背景色のデフォルトはグレーがかかっています。これだけで WKWebView への置き換えが可能ですが、後述する Cookie の共有ではこのロジックが使えなくなるのでコードでの生成も記載します。

var webView: WKWebView! = {
       let configuration = WKWebViewConfiguration()
        let webview = WKWebView(frame: .zero, configuration: configuration)
        return webview
    }()

computed property を使ってクロージャーの中で初期化宣言すればそのままプロパティとして利用できます。ただし、これだけでは画面上の view には乗らないのでどこかで乗せないといけません。

    override func viewDidLoad() {
        super.viewDidLoad()
        setupWKWebViewLayout()
    }

    private func setupWKWebViewLayout() {
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.topAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true
        webView.bottomAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
        webView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
        webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
    }

オートレイアウトを使いたいので translatesAutoresizingMaskIntoConstraints を false にしています。コードから生成する場合はこれで事足ります。

WKWebView でのページのローディングで WKNavigationDelegate を使うようにする

次にウェブブラウザのローディングで使うデリゲートメソッドが変わりますので、それぞれ置き換える必要が出てきます。ものすごく省略していますが、UIWebView と WKWebView とでこのように変わります。

/// UIWebView
extension ViewController: UIWebViewDelegate {
    func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
        guard let host = request.url?.host else {
            // ページのローディングを許可しない
            return false
        }
        // ページのローディングを許可する
        return true
    }
}

/// WKWebView
extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard let host = navigationAction.request.url?.host else {
            // ページのローディングを許可しない
            decisionHandler(.cancel)
            return
        }
        // ページのローディングを許可する
        decisionHandler(.allow)
        return
    }
}

大きく異なるのが UIWebView ではページの読み込みを許可するかどうかは Bool で判断するところが、 WKWebView では decisionHandler で処理するように変わります。 またロードしたページの url は request からではなく navigationAction から取得するようになりました。こちらは気をつければいいだけでした。

アプリ側で JavaScript 実行(同期)をしている箇所は、 WKWebView の場合は非同期(コールバック)になる

UIWebView での JavaScript 実行は同期的でしたが、 WKWebView では非同期的に変わります。

/// UIWebView
let string = webView.stringByEvaluatingJavaScript(from: "JavaScriptのスクリプト")

/// WKWebView
webView.evaluateJavaScript("JavaScriptのスクリプト", completionHandler: { result, error in
    if let result = result as? String {
        let string = result
    }
})

そのため、戻り値を String で返す関数がある場合はロジックが破綻してしまうので completionHandler などを用意して関数にコールバック引数を用意してあげる必要が出てきます。ちなみに非同期を同期的に無理やり変換しようとするとデットロックしてしまうので注意です。

ネイティブ画面でログインAPIを叩いている場合、 Cookie の共有の扱いに注意する

これが一番大変でした。 アプリ自体にネイティブ実装のログイン画面が存在しログインAPIを使ってログインしています。そして、レアジョブアプリはレッスンルームと教材の部分でアプリ内WebViewを使っています。このアプリ内 WebView はクッキー共有が前提の設計になっていて、ログインAPIで取得した sessionId をアプリ内 WebView に渡して表示させています。ですので、アプリがログイン中であればアプリ内 WebView はログインを維持していないといけないです。 (途中でセッションが切れてしまった場合はログインページを表示させています)

UIWebView でもこの Cookie 共有が実装されていましたので、 WKWebView でも Cookie 共有が実装されていなければなりませんでした。結論からいえば、これは解決しました。 UIWebView の時に Cookie のセットができていました。 HTTPCookieStorage に保存すれば共有できます。

    class func setCookie(_ cookie: [HTTPCookiePropertyKey: Any]) {
        let newCookie = HTTPCookie(properties: cookie)
        // HTTPCookieStorageにクッキーを保存する
        HTTPCookieStorage.shared.setCookie(newCookie!)
    }

このあとに WKWebView で保存した Cookie をセットしなければ行けなかったのですが、どうやってセットすればいいのかが情報が少なすぎてわかりにくかったです。

答えは WKProcessPool に存在していました。この WKProcessPool を WKWebView の WKWebViewConfigurationインスタンスにセットして WKWebView を生成すれば Cookie を設定できます。

extension WKProcessPool {
    static let shared = WKProcessPool()
}

class ViewController: UIViewController {
    var webView: WKWebView! = {
       let configuration = WKWebViewConfiguration()
        configuration.processPool = WKProcessPool.shared
        let webview = WKWebView(frame: .zero, configuration: configuration)
        return webview
    }()

    /*
     中略
     */
}

ちなみに WKWebView には configuration プロパティが存在するからコードではなく storyboard から @IBOutlet で接続してセットしなおせばいいのでは?と思われるかも知れませんが、 WKWebView のインスタンスを生成した後に WKProcessPool.shared を設定しても共有されません。

var webView: WKWebView! = {
       let configuration = WKWebViewConfiguration()
        /// これは共有される
        configuration.processPool = WKProcessPool.shared
        let webview = WKWebView(frame: .zero, configuration: configuration)
        /// これは共有されない
        webview.configuration.processPool = WKProcessPool.shared
        webview.backgroundColor = UIColor.white
        return webview
    }()

つまり、 Cookie を維持したい場合には storyboard にある WKWebView では実現できなく、コード生成でのみ実現可能です。これは謎の仕様ですが、ハマリポイントでもありますので注意が必要です。

最後に

このような手順を踏むことで WKWebView へのリプレイス作業が完了しました。 UIWebView に未練はありませんが、もうちょっと WKWebView の使い勝手を改善してほしいですね。まだ UIWebView を使っているアプリがあればこちらの記事が役に立てば幸いです。

補足

レアジョブアプリは RxSwift という外部ライブラリを使っていて、この RxSwift のライブラリの中でも UIWebView が使われていました。 正確には RxSwift の RxCocoa で UIWebView が使われています。

github.com

もちろん今回の Appleガイドラインにおいて、外部ライブラリに含まれる UIWebView も例外ではありません。 ガイドラインの対象になりますのでこの問題をどうしようかと悩んでいました。 ですが先月3月に、RxSwiftのライブラリがこのガイドラインに対応したバージョンをリリースしたそうです。

https://github.com/ReactiveX/RxSwift/releases/tag/5.1.0

RxSwift と RxCocoa のバージョンを 5.1.0 にアップグレードすると UIWebView がなくなっていることが分かります。 これで正式に UIWebView の撲滅が達成したことになりますね!

では、引き続きレアジョブアプリの改善を続けていこうと思います。いつもながら長文になりましたが最後まで読んでいただきありがとうございます。

在宅勤務での取り組み&在宅でのナレッジ

こんにちは、CTOに続き一年ぶりとなりましたレアジョブとディズニーをこよなく愛している岩堀です。
基本弊社は在宅勤務になり、ディズニーも休園が続いて、家からほとんど出ることもないので、 仕事中もディズニーの音楽かけて夢の国にいる気分で仕事をしています。 皆さんはどのように在宅勤務をこなしていますかね?

今回は弊社での在宅勤務の取り組みについてと、ちょっとした在宅に役立つナレッジを書きたいと思います。

社内の取り組み

弊社は緊急事態宣言を前に全社にて在宅勤務へ移行しました。 また弊社の海外子会社は日本よりも先にロックダウンが発生したことにより、 強制的に在宅勤務に移ることになりました。 準備もできていないうちに在宅勤務に移ったことで社内システムを担当する ITソリューションチームでは現在も一丸となって対応しております。 それでは在宅勤務が行われる前と後でどのように変わったか、少し書きたいと思います。

在宅勤務前の状況

弊社はオフィス勤務がメインでした。各ツールに対してもオフィスからのIPに制限しておりました。 外部にいるときもVPNを経由して、各ツールへアクセスを行っておりましたので、 外部からアクセスが必要な社員に対してのみVPNの設定を行っておりました。 これは海外の子会社でも同様の対応で行っておりました。

在宅勤務前
before environment

在宅勤務後の状況

在宅勤務後は基本的にオフィスでの業務がなくなりますので、すべての従業員がVPNでつなぐ状況となりました。

在宅勤務後
after environment
急遽在宅勤務の環境へ移行したことで問題となってきたのは2点ありました。

  • VPNライセンス数
  • VPN機器のリソース負荷

弊社ではVPNとしてCiscoのAnyConnectを使っており、ライセンス数は利用されるユーザー数分必要になります。
元々全従業員がVPNへ接続することを想定していなかったので、全従業員数分のライセンスの購入は行わず、機器の最大接続数分のみを購入しておりました。
ライセンスの追加購入を考えていたところ、CiscoのほうでCovid-19による在宅勤務への移行をサポートするように、 期間限定のライセンスを無償でプログラム提供しているのを知り、こちらを利用してライセンス数をクリアしようとしております。

www.cisco.com

またVPN機器のリソースは定期的に監視を行っており、負荷状況が高い状態であるので、こちらに関しては何かしらの対応を行っていこうと思います。 現状、VPNを利用する前提として、Web会議はVPN機器のリソースだけでなく通信トラフィックも無駄に使ってしまうため、VPNを経由しないようにしていただくことと、VPNが不必要な状態のときには切ってもらうように周知しておりますが、やはり会議で必要な資料を開く上でVPNが必要な場合もあるので、VPN接続し続けた上で行われることがあるようです。このあたりの回避できる方法を考えていかなければないですね。 

在宅勤務でのナレッジ

在宅勤務へ移行したことで、リモートにてヘルプデスクとして数々の問い合わせいただく中で、 在宅で勤務をする上で家での環境が整っていない状況が数多くあるということを知りました。 そこで、現在の環境でも快適な物にできるようにちょっとしたナレッジを書きたいと思います。

通信環境

今回、このような状況になって知ったのですが、現在自宅にネット回線を引いていない方が結構いるということを知りました。 その方たちは在宅で仕事する際にはスマホテザリングなどで対応しているようですが、通信容量が多くなって通信制限にかかってしまう方がいらっしゃるようです。 そこで在宅ワークにおいてテザリングを使う中で通信容量を抑える方法をあげたいと思います。

動画やWeb会議などはあまり利用しないのは当然となりますが、業務でどうしても必要な状況があります。 その場合にはVPNは経由しないほうが通信容量は抑えることができます。 VPN通信には通常の通信と比べて暗号化等を行っているため付加情報がついており、通常よりも通信容量が増えます。 検証のため自分の端末を使って、みんな大好きなミニーのショー動画(2分半)を見ながら試してみました。

www.tokyodisneyresort.jp

テザリング端末での通信容量チェッカーアプリを使った大雑把な値ではありますが、VPNなしのほうが10MB程度節約になりました。

通信環境 通信容量
VPNなし 29MB
VPNあり 42MB

実際Web会議をされるときは長時間となると思いますので、可能な限りVPNは経由しないほうが、 通信容量を抑えることができます。

またブラウザを開いているだけで通信が行われることもあるので、PCからしばらく離れる場合には テザリングをOFFにして通信を行わないようにしておくことでチリツモを防げますので大事かと思います。

作業環境

オフィスではラップトップの他にディスプレイも割り当てられて、拡張ディスプレイを使って、 業務をやられている方も結構いるかと思いますが、在宅勤務でも同じように行う上で、ディスプレイの購入をされた方もいるかと思います。
私も買おうかと思いましたが、置くスペースもないので、家にあるものでどうにかならないか考え、テレビにHDMIケーブルを使って外部ディスプレイとして使うことを考えました。

ただガジェット好きな私の家では既にHDMIポートをフルに使っており、毎回抜き差しするのも面倒と思ってしました。 そこで思いついたのが、FireStickTvをAirPlayとして使ってできないか。

早速やってみたところあっさり行うことができました。

ケーブルもなくスッキリした状態で作業ができるので、結構便利です。 やり方は以下の手順で実施しました。

  1. FireStickTVにAirPlay用アプリをインストールする
    私は評判の良かったAirReceiverを入れました。

  2. AirReceiverでAirPlayをOnにする

  3. MacにてAirPlay検索より該当のディスプレイを選択(この場合はAFTT-26)

  4. 拡張ディスプレイにするために、「個別のディスプレイとして使用」を選択

これであっという間に拡張ディスプレイとして機能します。ちなみに裏で別アプリを動かすことも可能なので、AmazonMusicをかけながら拡張ディスプレイとして使うことができるようになります。

私のマシンはMacなのでAirPlayでやりましたが、Windowsの場合はMiracastで同じことができます。 こちらはアプリをインストールすることなくFireStickTVのミラーリング機能で対応可能です。
こちらについては別の機会で記載したいと思います。

ぜひ皆さんも快適な在宅勤務をして、このコロナに負けずに頑張りましょう!

国境を超えてスクラムを実践する

こんにちは。一年ぶりの登場となりますCTOの山田です。

さて、RareJob Tech Blogも無事一年が経ち、当初の宣言通り週1ペースで様々な記事が投稿されてきました。また、この一年、レアジョブは様々な変化がありました。中でもレアジョブ英会話のシステムリニューアルの意思決定をしたことが開発部門にとって大きな変化です。背景や目的としていることなどはRareJob Appealの記事をご覧下さい。

このシステムリニューアルPJでは、日本だけではなくフィリピン開発部隊との連携が必要となります。レアジョブは、連結子会社6社ありますが開発部門を持っているのが日本のレアジョブとフィリピン ケソン市にオフィスを構えるRareJob Philippines, Inc.の2社だけですので、この2社間の連携が非常に重要です。この2社間でどのような体制、開発プロセスでシステムリニューアルPJを進めるのかを紹介したいと思います。 国を跨り協働開発する際の参考になればと思います。なお、RareJob Philippines, Inc.ではレアジョブレッスンを提供する講師に関わるすべてのシステム開発・運用を担っております。

開発組織間の連携

リニューアルプロジェクトでは、多くの不確実性と変化が伴うためスクラムで開発を推進していきます。アジャイル開発は経験主義を基本としているので、いきなり大きなプロジェクトを立ち上げてもうまく行きません。普段からどのような仕組みでコミュニケーションを取っているかで立ち上がり方が違います。言語、文化、時差など様々な違いをどう吸収するかを考えながらこれまで組織を構築してきました。現在では各レイヤーでコミュニケーションを密に取り、レアジョブ英会話サービスを共に支える組織となりました。
日本とフィリピン共に機能組織としての技術本部があり、ほぼ同じような組織構造で構成しております。

体制イメージ
各レイヤー間の連携

  1. Engineer Manager間での情報共有
    RareJob Philippines, Inc.の開発責任者へは週次で会社の動きを共有。特に日本を軸に計画が大きく変わることがあるため、影響ある情報をいち早く伝えることを心がけています。
    また、Engineering Manager間は月一で情報共有会を開催。プロジェクトの動きや課題の議論などをこの場で行ないます。もちろん必要であれば随時オンラインミーティングを開催しています。

  2. Lesson Roomの協働開発
    Skypeに変わるWebRTCを利用したレッスン提供機能であるLesson Roomを生徒側は日本、講師側はフィリピンで開発を行っております。
    コアとなるロジックを共通化しているため、密に連携しながら生産性を向上させる取り組みをしております。

  3. 生徒向け、講師向けに関わる開発、リリース情報の共有
    基本的には双方のサービス要件に沿ったドメスティックな案件が多いです。ただし、現在は共通のDBを利用して生徒、講師システムが動いておりお互い影響し合うため、リリース情報の共有は会議やチャットなどで行ないます。システムリニューアルPJでは、この共通DBをどのように分割するかを議論しています。

  4. レアジョブグループの共通基盤協働開発
    Golangで開発されたプロダクト共通の情報資産を有する共通基盤。各プロダクトからはAPIで利用可能で、レアジョブ英会話サービスでは、リニューアルプロジェクトを通じて一部の機能を共通基盤へ移管していきます。この共通基盤は、生徒システム、講師システム双方で利用するため、協働で開発しております。

  5. AWSの協働運営
    レアジョブでは、複数のプロダクト、メディア、社内向けのサブシステムなど多くのシステムが稼働しています。講師の給与計算などフィリピン固有の重要なシステムもあります。これらを安定的に運営していくため、ルールや権限など厳格に設計し、協働で運営してます。

システムリニューアルPJの進め方

プロトタイプとしてMVPを創る

レアジョブ英会話も気がつけば多くのお客様に利用されている規模の大きなシステムとなりました。この規模のシステムリプレイスをいきなり進めるには、多くの手戻りが発生することが容易に想像でき、またビジネスへの影響も広範囲に渡るため、2019/11〜2020/3まではプロトタイプという位置づけでレアジョブ英会話のMVPを開発しました。
このプロトタイプフェーズでは、MVPの開発だけではなく以下も行ないました。

国境を超えてスクラムを実践する

プロトタイプ開発での経験を経て、この時の最終的な仕組みをベースに、フルバージョンの開発は下記のような整理をしました。

体制

  • 4つのスクラムチームから構成(日本とフィリピンの混同チームもある)
  • 全体の意思決定者であるプロダクトオーナーがおり、各スクラムチームにチーム毎の意思決定をするサブプロダクトオーナーを配置
  • スクラムチームに技術的な意思決定をするテックリードを配置
  • 全チームのプロダクトオーナー、テックリードからなるステアリングコミッティを設置

Global TimelineとPBL

  • 開発効率観点から開発していく順をEpic単位で定義したGlobal Timelineを策定し、ステアリングコミッティで全体の進捗を管理する
  • ユーザーストーリーを一元管理し、PBLに分解し各スクラムチームで開発を担う

スプリントとスクラムイベント

  • 期間は2週間で、開始、終了をすべてのチームで合わせ、スクラムイベントを同日に一斉開催する
  • スクラムチームでのプランニング前に全体プランニングとして、次スプリントで必要となるPBLを他チームへ依頼する
  • スクラムチームの振り返り後、全体での振り返りを実施

全体像

システムリニューアルPJの進め方全体像
システムリニューアルPJの進め方全体像

その他の取り組み

  • 共通の課題リストを持ち、PJ全体で解決に向けた取り組みを行なう
  • マイクロサービスとは言え、原則の技術選定に準ずる
  • 運用が複雑化しないようにCI/CD、監視などの仕組みを統一していく
  • 利用するツール(ドキュメンテーション、コミュニケーションツールなど)を統一する
  • 原則英語でドキュメントを書き、分散させない
  • 各自英会話スキルをレアジョブ英会話で磨く
  • 定期的に出張しFace to Faceで理解を深める(新型コロナ問題で当面厳しそう)
  • 背景、目的、方針は何度も同じことを伝える
  • 技術顧問の広木さんに、移行方法をレビューして頂く(第三者がレビューすることで気づきが多い)

最後に

システムリニューアルPJでは、モノリシックアーキテクチャからマイクロサービスアーキテクチャへとシステムそのものを変えていきます。これだけでも難易度が高いですが、フィリピン開発部隊と進めていくことでさらに難易度が上がります。
また長期プロジェクトになるため、問題が発覚したらすぐに解決に向けて動く必要があり常に神経を尖らせて、関係者と密な連携を取り進めていきます。 このプロジェクトを通じてレアジョブグループ全体の開発機能が一段高いレベルに上がることを確信しており、またこれから立ち上がる新規事業へも貢献出来ると思います。

ということで、また来年(!?)お会いしましょう。