目次
概要
会社の技術メンターシップ制度で当時入社して3〜4ヶ月の新人エンジニアのメンターを半年程度つとめていました。その過程でiOSアプリ開発を一緒にやっていたのでその時のことについて書きます。
技術メンターシップ制度の概要
- 2019年から実施
- 実施する目的はメンティーに1人前のエンジニアになってもらうこと
- 定義が曖昧ですが、ざっくり言うと弊社の開発プロジェクトに入った時に先輩の助けがほぼなくてもやれるレベルへの到達を目指していました。 なお、1つのプラットフォームに限った話です。
- 期間は半年間
- メンティー1人につき、メンター1人の体制
- メンティーは半年で達成する目標を立てる
- メンティーは月に1回外部の勉強会に参加する
- メンティーは月に1回Qiitaの記事を投稿する
- 2週間に1回程度メンターとの1on1を実施
メンティー(新人エンジニア)のスペック
前職はオフィス家具の販売をやっており、Swiftを半年ほど勉強して未経験で転職。
MVCで6〜7画面程度の簡単なアプリは作成した経験あり。
弊社では業務に入ったばかりだったので保守案件で簡単な業務を中心にやっていました。
アプリを開発するまでの経緯
最初は頃は、1on1をしていても仕事での不安なところや開発のフロー、クライアントとのやりとりの仕方などスキル向上とはあまり関係ないようなことも話していました。また、業務時間にやっていたとはいえ、悪い言い方をすると主業務の片手間でやっていたのでメンターである自分もそこまでコミットメントしていない状態でした。
そんな中、他のメンティーがメンターを積極的に巻き込んですごく良いやり方をしていると聞いたため、
すぐにその方法を取り入れました。そしてメンティー君とも話した結果、その中の1つにあったアプリをスクラッチで開発することを重点的に進めていくことになりました。
iOSアプリ開発の内容
- 期間は3ヶ月
- git-flowに従い、メンティーがチケットをきって、その内容を基にPRを作成してメンターがコードレビューという流れで開発
- デザインや基本機能は弊社研修で作成するアプリをベースに1からMVVMで実装する。
-
弊社のプロジェクトで採用している設計パターンはMVVMやClean Architectureが多かったため、まずはMVVMに慣れてもらおうと思って採用しました。 - メンティー君が自分で追加したいと思った機能や画面も作成する。
やってみた結果
iOSアプリは無事完成😃
メンティー君が自分で立てた以下の3つの目標は達成できました✨MVVMで開発する、RxSwiftの概念を理解する
チュートリアル画面など多少手の込んだ画面を作れるようになる
業務で入っているプロジェクトでissueを10件以上自力でこなす
反省点
Repositoryクラスを無駄に作ってしまったり、ViewControllerに処理を固めてしまったり、クラス毎の責務をうまく分割できなかった点
書き方を知れば解決できることが多かったのでコードレビューで対応。 ViewModelクラスやUIクラス、Routingクラスを作って責務を分割したことで数百行あったViewControllerは以下のようにある程度薄くなりました。
(コード抜粋)
final class SignInViewController: UIViewController {
private lazy var ui: SignInUI = {
let ui = SignInUIImpl()
ui.viewController = self
return ui
}()
private lazy var routing: SignInRouting = {
let routing = SignInRoutingImpl()
routing.viewController = self
return routing
}()
private var viewModel: SignInViewModel
private let disposeBag: DisposeBag = .init()
init(viewModel: SignInViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
// ここ本当はfatalErrorしたくない
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// 画面遷移やレイアウト作成の処理を他のクラスに書くことでViewDidLoadの中を薄くしたい。
ui.setupUI()
bindUI()
}
}
private extension SignInViewController {
private func bindUI() {
let input = SignInViewModel.Input(
didSignInButtonTapped: ui.didSignInButtonTapped.asObservable(),
didSignupButtonTapped: ui.didSignUpButtonTapped.asObservable(),
emailText: ui.emailTextFieldText.orEmpty.asObservable(),
passwordText: ui.passwordTextFieldText.orEmpty.asObservable()
)
let output = viewModel.transform(input: input)
output.isValid
.bind(to: ui.isSignInButtonHidden)
.disposed(by: disposeBag)
// driverでハンドリングしてもよかったが、最初だったのでobservableで対応
output.result.subscribe(onNext: { [weak self] _ in
self?.routing.showMainTab()
}).disposed(by: disposeBag)
output.error.subscribe(onNext: { [weak self] error in
self?.createAlert(message: error.localizedDescription)
}).disposed(by: disposeBag)
ui.didSignUpButtonTapped.subscribe(onNext: { [weak self] _ in
self?.routing.showSignUp()
}).disposed(by: disposeBag)
}
}
ViewModelの実装方法で混乱してしまった点
ネットにある情報をみるとViewModelはいろんな実装方法があってどれにしたらいいのかわからないと悩んでいたのでViewModelについては以下のリポジトリの設計パターンで統一することにして解決。 Inputとoutputがそれぞれまとめられ、ストリームも分割されてきれいになりました。
参考にしたリポジトリ
(コード抜粋)
final class SignInViewModel {
private let dependency: AccountDataStore
init(dependency: AccountDataStore) {
self.dependency = dependency
}
}
extension SignInViewModel {
// inputを定義
struct Input {
let didSignInButtonTapped: Observable<Void>
let didSignupButtonTapped: Observable<Void>
let emailText: Observable<String>
let passwordText: Observable<String>
}
// outputを定義
struct OutPut {
let result: Observable<SignInAPI.Response>
let error: Observable<Error>
let isValid: Observable<Bool>
}
func transform(input: Input) -> OutPut {
let emailTextRelay = input.emailText.map {
$0.count >= 6
}
let passwordRelay = input.passwordText.map {
$0.count >= 6
}
// バリデーションは今は簡易にしているが、正規表現など使いたい
let isValid = Observable.combineLatest(
emailTextRelay,
passwordRelay
) {
$0 && $1
}
let parameter = Observable.combineLatest(
input.emailText,
input.passwordText
)
let response = input.didSignInButtonTapped
.withLatestFrom(parameter)
.flatMap { (email, password) -> Observable<Event<SignInAPI.Response>> in
let info = AuthModel(
email: email,
password: password
)
return self.dependency.SignIn(with: info)
.materialize()
}
.share(replay: 1)
/*
ViewModelでAPI通信をハンドリング
結果をresponseとErrorに分割してViewControllerに渡す
*/
return OutPut(
result: response.elements(),
error: response.errors(),
isValid: isValid
)
}
}
RxSwiftの概念や使い方を掴むまでに時間がかかってしまった点。
体系的に理解して欲しかったので、まずなぜ使うのか、Rxがどういったものなのかを口頭や図を交えながら説明。その後ペアプロをしてまず簡単なところで動かしてもらい、機能実装してもらうという流れで教えていました。ただ、1回か2回説明しただけだとどうしても忘れてしまうのでそこはコードレビューなどで補完するようにしていました。
モチベーションが続きにくかった
これは意外と大きかったです(笑)やはり業務と並行してやるとなると平日や土日などに空き時間を見つけてやることになり、モチベーションが途切れてしまい進捗があまりでないことが多かったです。 この問題については、先に作業の予定を入れてしまうこと、誰かと約束させることで強制力をもたせるということをして解決しました。 業務後にメンターシップのアプリ開発をする時間を毎日30分程度予定に入れて、自分も詰まった時に質問に答えるために一緒にやっていました。
新規機能追加が一部できなかった
スケジュールに追われてしまい、Firebase/AWS S3との連携やカメラ機能など当初メンティー君が実装しようとしていた一部の機能が実装できずに終わってしまいました。 しかし、「期限までに間に合わないからどうしようか?」という話をした時に「この機能はここまでやりましょう」「スケジュール的に厳しいのと実装的にこの辺りが難しいので〜〜機能は今回は捨てましょう」といったように意思決定して進めることができてこの辺は非常に良かったです。
まとめ
メンターシップ制度を通して、仕事のやり方や開発手法などを伝えられたのは非常によかったです。
また、メンター視点でもメンティー君のレベル感がわかったこと。教えるために色々準備して自分の知識定着につながるなど技術メンターシップ制度実施したことでの副次的な効果もありました。
ただし、本来の目的である 1人前のエンジニアになってもらう
ということを考えた時に以下の点でやりきれなかったなと思っています😢
- エラーハンドリング
- オフライン対応
- 証明書関連
- 本番環境を作成してリリース
- イベントやクラッシュのトラッキング
- CIの導入
etc…
なので今後はこの辺も含めてよりスキルの高いエンジニアになってもらえるよう色々と改善しようと思ってます☀️
最後まで読んでいただきありがとうございました!