株式会社はてなに入社しました
APNs証明書に「証明書は信頼されていません」と出る問題の解決法
横断型テックリードという働き方
みなさんこんにちは。FromAtomです。
自分は今『モバイルアプリ分野テックリード』という肩書で仕事をしています。世の中のテックリードの皆様におかれましては「"分野" テックリードって?」「横断型テックリードって?」という感じかと思います。そんなテックリード業を2019年末から2年ちょっとやってきたので、この記事では自分がやっていることの説明と、分野テックリードが置かれた経緯を紹介します。
なぜモバイルアプリ分野テックリードが必要になったのか
弊社では、全部で12つのiOS・Androidアプリを開発しています。これだけあると、各アプリのプロダクトオーナー毎に意思決定の精度にばらつきが出てきます。例えば、Appleのレビューリジェクトに対する姿勢が異なると「まぁ適当にごまかして通せば勝ちや!」というチームと「BANリスクもあるからちゃんとやるか。面倒だけど。」というチームが生まれる可能性があります。
Apple的にはアプリが別でも提供してる会社が同じなら同じ扱いをするので、1つのアプリのレビュー対応が粗野なせいで巻き添えを食って会社のアカウント自体BANされ、全アプリ死亡してしまう可能性もあります。ここまで極端なズレはないんですが、なにも手を打たなければここに至ってしまう可能性もあるわけです。こういったズレを無くして全社的に意思決定を担う役割が必要になります。
また、Webからサービスインした企業の多くは、Web技術をメインとしていたエンジニアがCTOをしていることが多いと思います。「Web屋にスマホアプリが分かるかよ!」みたいな話がしたいわけではなくて、ひとりの人間がカバーできる技術範囲には限界があるということです。
スマホアプリという技術領域は単純なプログラミング能力だけでなく、AppleやGoogleというプラットフォーマーによる制約をどのように解決していくかという能力も必要になります。例えばアプリのリジェクトに対する対応方法でも、「レビュアーがこういう文面でリジェクトしてくる場合の対応はこう」といった、ググっても出てこない経験値が効いてくる場面が多々あります。また、WebでGDPR対応が大変なように、iOSでのATTやAndroidでのData Safety Sectionなど、担当のアプリエンジニアだけでは解決が難しい課題もしばしば発生します。
そのため、モバイルアプリ分野テックリードというミニCTOのような役割を設けることで、技術や経験に基づく判断や全社的な方針決定を素早く行うことが可能になります。
ぶっちゃけ弊社のCTOは頭良くてバチバチに優秀なので、自分がいなくてもスマホ分野も余裕でカバーできそうなのですが、いかんせん『人間の身体を持っている』という弱点があります。24時間365日寝食休憩排泄皆無で働けるのであれば良いのですが、人間はそれをすると死んでしまいます。そういう意味では、適切に役割分担ができていると感じています。
事業部型テックリードと横断型テックリード
弊社には事業部型のテックリードと横断型のテックリードの2タイプのテックリードがいます。事業部型テックリードは、各事業部もしくは部に所属しています。例えばプロダクトAやサービスBのテックリードみたいな感じですね。世の中的にはこちらが一般的なテックリードだと思います。プロダクトやサービスとしての技術的戦略と、会社全体としての技術的戦略でブレがないようにすり合わせ、その思想や方針をチームに浸透させ牽引していく役割を担うことが多いでしょう。そのため、事業部型のテックリードはプロダクトオーナーやマネージャーと密に連携して、目指すべき方向性を示したり、将来を見据えた先行投資を技術面から行っていきます。
このタイプのテックリードについてはこちらの記事が参考になります。
テックリードという役割 | by Shimpei Takamatsu | Medium
一方で横断型テックリードは特定のプロダクトやサービスに所属せず、特定の技術領域を担当しています。技術領域型テックリードとも言い換えられそうです。モバイルアプリ分野のテックリードはこれにあたります。特定のプロダクトやサービスに属さないため、サービスの成長戦略・人材の育成・キャリアパス・人事評価などには関与しません。特定の技術領域(例えばアプリ)に携わるエンジニア全員が、統一された意思決定方針をもって働けるように、技術的な方針を言語化し布教する仕事をします。
モバイルアプリの横断型テックリードがやってること
アプリに関する技術的な相談窓口
自分で答えられる範囲なら答えたり、明るくない分野については「Aさんが詳しいですよ」と伝えています。リジェクトに対する対応方針とATT関係の相談が多いです。また、経営判断が必要だったりセキュリティ上の懸念がある場合は、然るべき役職へのエスカレーションも行います。
言語化・文章化
アプリエンジニア目線から見た常識を、経営層やマネジメント層にも分かるように言語化・文章化しています。今までに文章化した内容としては、
- 人員計画
- 中長期的技術戦略
- アプリを作る・作らないの判断基準
- レビューリジェクトとの向き合い方
- PWAがネイティブアプリの代替にならない理由とPWAの活かし方
- なぜ弊社はネイティブアプリで開発をするのか
などがあります。入社すると読めます。
スマホアプリエンジニア全員1on1
弊社には10人程度1の正社員アプリエンジニアがいるのですが、半年毎に全員と1on1をしています。普通のキャリアや悩み系1on1はチームのマネージャーやメンターが行っているのため、この1on1では、スマホアプリ開発面での困り事をヒアリングすることを主目的にしています。
例えば、「ビルドが遅くて……」と言われたらPCのスペックを聞き、不足しているようなら2両手を振り回しながらマネージャーに「こいつに新しいのを買ってあげてください!メモリはなにも考えずに一番多いので!」みたいな動きをしています。
法律・プライバシー・レビューガイドライン・プラットフォーマー対応
多分ここが一番大きい業務領域です。わかりやすい例ではATTやData Safety Sectionなどで、提出する情報を揃えるために奔走します。なにぶんアプリ数が多く、それに伴ってステークホルダーも増えるため、説明・調整・調査・ヒアリングをぶん回しまくることになります。
他には、どこかの国が法律を変えると、それに伴ってAppleやGoogleが書類提出やフォーム入力を求めてくるのでその対応方針を決めたり、レビューガイドラインの変更に伴った要件定義と各所への実装依頼を出したりします。
最近では、Appleがレビューガイドラインでアプリ内にアカウント削除機能を求めてきたので、その対応方針やリジェクトリスクの検討をし、実装方針をあちこちと相談して決めたりしてました。余談ですが、Appleの指定した時期に間に合うように急いであれこれ調整したのに、結局直前で延長されたので「おまえおまえおまえぇ!」ってキレました。
モバイルアプリの横断型テックリードがやらないこと
アプリの機能変更に関する意思決定
アプリに新しく機能を加えたり、削除したりといった意思決定は、そのアプリを開発しているプロダクトオーナーが意思決定するべきです。自分はあくまでも「なにか困ったら聞いてね」という存在でいます。もちろん「それリジェクトリスクが高いですよ」といったものは口をだすようにしています。
マネジメント
テックリードはマネージャーではありません。あくまでも現場にいるチームメンバーの目線から、プロジェクトやチームをゴールに向けて技術面でリードしていくのが、テックリードの役割です。そのため、給与に関する評価を行ったり、エンジニアの抱える技術的ではない問題を取り除くのは、役割の外になります。もしその仕事をテックリードがするのであれば、マネージャーが不要になってしまいますからね。
なおかつ、横断型テックリードにはマネージャー権限・人事権がありません。「横断的に特定職種のエンジニアに関わる諸問題を解決してくれる」と勘違いされがちですが、実はそこまでの権限がないので動けません。各事業部のアプリに関する問題や事業部をまたいだタスクの依頼、人員の配置などの問題を主導して解決するのはあくまでも事業部です。これを自分が行ってしまうと、コミュニケーションパスがハチャメチャになってしまうので、意識してやらないように気をつけています。
もしその仕事を横断型テックリードに求める場合、関係するエンジニアを全員集めて1部署を作る必要があります。たとえば「アプリ部」みたいなものを作り、テックリードがアプリ部エンジニアリングマネージャーとして働く形になると思います。こうすれば、アプリ分野テックリードがマネージャー権限を得られるためアプリエンジニアのタスク管理をしたり、各部署へのリソース配分を調整することができます。ただ、弊社的にはこの職能縦割り方式はまだ規模的に早いので、現時点で導入する気はありません。
一方で、「アプリ基盤」のような部署を作るのはありかもと考えています。プロダクト開発を行うエンジニアとは別に人員を確保して、各プロダクトの開発速度を底上げしていく感じですね。その人員を確保するのが難しいのですが……。
キャリアパスやチャレンジの支援
すべての事業部に所属しているわけではなく、情報が不足しすぎているため実質不可能です。というか、ここらへんの領域は事業部型テックリードやマネージャーが考えてくれているので、自分がやる必要はないんですよね。もちろん、自発的に動いていないだけなので、相談されたら1on1などをしています。
なんかすごいライブラリとか作る
基本的には自分で手を動かすのではなく、作りたい人が作れる環境構築をするのがメインだと考えています。
仮説・検証方法・実装の妥当性やビジネス面での利点などを確認した上で、CTOや偉い人にかけあったり、必要な物資を調達します。もちろん、かけるコストに対する対価が得られることが前提ですが、そこらへんをいい感じにする仕事もします。事業部型テックリードがやってくれる場合もありますが、判断が難しい場合もあります。
まとめ
モバイルアプリ分野テックリードという横断型テックリードの紹介と、生まれた経緯を紹介しました。スマホアプリ開発はコードを書く以外の面倒事があまりにも多いです。その面倒事をなるべく引き受けることで、スマホアプリ開発者がアプリの改善に集中できる環境を作ることを目指してテックリード業を行っています。
もし、こういった環境で働いてみたいという方や、自分がテックリードやってやるぜという方がいましたらTwitterで @FromAtom までご連絡ください。ただ話を聞いてみたい方や、カジュアル面談(突然面接が始まるタイプではない)をしてみたいという方もぜひぜひ〜。
最後に、文字ばかりの記事を読んでいただいて、ありがとうございました。
WordPressにOAuth認証してRubyでREST APIを叩いて記事投稿する。
やりたいこと
手順
OAuth用のプラグインを入れる
このプラグインを入れるだけでOK
アプリケーションを登録する
WP管理画面のメニューの[ユーザー] > [Applications] を開く
こういった画面が表示されるので、中身を入力していく。
- Consumer Name
- アプリの名前。なんでも良い。
- Description
- わかりやすい説明。自分で後からなにに使っているか分かるように書いておく。
- Callback
入力が終わって Add Consumer
ボタンを押すと
と Client Key
と Client Secret
が表示される。これは後で使うのでメモっておく。他人には知られないようにする。
RubyでAccessTokenを取得する
Gemfileはこう
source "https://rubygems.org" gem "oauth"
メインのコードはこんな感じ
require 'oauth' require 'readline' CONSUMER_KEY = 'XXXXX' CONSUMER_SECRET = 'XXXXXXXXXXXX' WP_OAUTH_ENDPOINT = 'https://[あなたのWPドメイン]/' REQUEST_TOKEN_PATH = '/oauth1/request' AUTHORIZE_PATH = '/oauth1/authorize' ACCESS_TOKEN_PATH = '/oauth1/access' CALLBACK_URL = 'https://[あなたのWPドメイン]/success' consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => WP_OAUTH_ENDPOINT, :request_token_path => REQUEST_TOKEN_PATH, :authorize_path => AUTHORIZE_PATH, :access_token_path => ACCESS_TOKEN_PATH) request_token = consumer.get_request_token(oauth_callback: CALLBACK_URL) puts '下記のURLにアクセスして認証を行う。`oauth_verifier` というクエリパラメータがあるのでコピペして入力する。' puts request_token.authorize_url oauth_verifier = Readline.readline("oauth_verifier >") access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier) puts "token: #{access_token.token}" puts "secret: #{access_token.secret}"
メモっておいた CONSUMER_KEY
, CONSUMER_SECRET
, CALLBACK_URL
を入力してから実行する。
REQUEST_TOKEN_PATH
などでPathを変えているが、これは https://[あなたのWPドメイン]/wp-json
にアクセスすると取得できるJSONの "authentication" > "oauth1" に記載されているので、うまく動かない場合は確認すると良い。
このコードを実行するとTokenとSecretが取得されて表示されるので、あとはそれを利用してREST APIを叩けば良い。
WPのREST APIを叩いて新規投稿する
上で生成した consumer
や access_token
をそのまま利用する。
require 'typhoeus' require 'oauth/request_proxy/typhoeus_request' hydra = Typhoeus::Hydra.new uri = 'https://[あなたのWPドメイン]/wp-json/wp/v2/posts/' oauth_params = { consumer: consumer, token: access_token, request_uri: uri } data = { title: "タイトルです", content: "記事の本文です" } req = Typhoeus::Request.new( uri, method: :post, params: data ) oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(request_uri: uri)) req.options[:headers]["Authorization"] = oauth_helper.header hydra.queue(req) hydra.run response = req.response
これで新しい記事が下書き状態で追加される。最初から公開状態にしたり、CategoryやTagを追加することもできる。詳しいAPIの使い方やパラメータは、公式のハンドブックを見ると良い。
SwitchBotを使って、加湿器の水を入れ忘れないようにした。
モチベーション
加湿器、水を入れ忘れると乾燥した部屋でMTGなどを続けることで喉が破滅しがち。忘れずに水を入れていきたい。
できたもの
こんな感じで、湿度が30%未満だったらSlackに通知されるようにした。加湿器に水が入っていれば30%は超える家の環境なので、30%をしきい値にしてある。
使ったもの
SwitchBot温湿度計で計測した値をHub Mini経由でAPIを叩けるようにしている。Hub Miniがないと温湿度計の値はアプリのBluetooth経由でしか取得できないので、APIで値を取りたい場合は必要になる。
セットも売ってるので、SwitchBot Hubがない場合はこっちを買うと便利。
構成
APIを叩いて、湿度を確認して、SlackにWebhookで通知するというスクリプトをRubyで書いて、それをHerokuでHeroku Schedulerを利用して定期実行しているだけ。
APIの使い方は下記記事が参考になるので、こちらを見てもらうと良い。
自動で泡のハンドソープが出てくるやつを買った。
これを買った。今までは手動でハンドソープを出していたけども、毎日何度もプッシュするのが面倒だったので購入。
この商品を選んだ理由としては、
- 充電式
- 乾電池入れ替えるの面倒なので
- バッテリーがへたる頃には本体自体がへたって買い替えだろうなと思ってる
- 出てくる泡の量が調整できる
- 「全然少ない」とかなりそうなので、調整できるのが良かった
- ボトル容量が大きい
- 詰め替えは面倒なので大量に入れておきたい
- 詰替しやすい
- ボトルを外して取って流し込むだけなので簡単
- 口も広いので良い
- ホワイトでシンプルな形でシルバーが入っていない
- キラキラかっこいいって要素は不要なので
やっぱり、自動で丁度いい量のハンドソープが出てくるのは便利。
Amazonレビュー見てると数ヶ月で壊れたってのがチラホラあるので、どうなるかなと思いながら使っていきます。
複数のライブラリを含むPackageをSwiftPMで利用する方法。
環境
- Xcode 13.2.1
- Swift 5.5.2
やりたいこと
The Composable Architecture や Firebase iOS SDK のように、XcodeのPackage Dependencies経由で入れるとこんな感じで複数のライブラリが内包されていて、使いたいものを選択して利用するタイプのライブラリ。
これを Package.swift
でも選択して利用したい。
やりかた
たとえば、The Composable ArchitectureのComposableArchitectureだけを利用したい場合は、こうやって Package.swift
に書けば良い。
import PackageDescription let package = Package( name: "SamplePackage", platforms: [.iOS(.v13)], products: [ .library( name: "SamplePackage", targets: ["SamplePackage"]), ], dependencies: [ .package(name: "swift-composable-architecture", url: "https://github.com/pointfreeco/swift-composable-architecture", .upToNextMajor(from: "0.32.0")) ], targets: [ .target( name: "SamplePackage", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture", condition: .when(platforms: [.iOS])) ]) ] )