https://www.youtube.com/watch?v=hh7Pru_nFqQ
引き続き API Design Guidelines
の 明瞭な使用を促していく
観点でガイドラインを眺めていきます。今回は 自由度のある型情報を補う
ための指針から、続けてじっくり見ていきましょう。どうぞよろしくお願いしますね。
————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #14
00:00:00 開始 00:00:20 前回のおさらい 00:00:54 自由度のある型情報を補う 00:02:48 ベース名や引数ラベルで情報を補う 00:10:30 型情報で説明してみる 00:17:50 主体として扱うものではないとき 00:24:26 クイックヘルプ・インスペクター 00:25:07 キーパスの扱い 00:29:51 前置詞に名前を添える具体例 00:33:21 ベース名とラベル名の使い分け 00:38:23 オーバーロード 00:39:56 イニシャライザーとファクトリーメソッド 00:42:45 前置詞の扱い 00:48:24 Objective-C での命名規則 00:50:52 おさらい 00:54:52 SwiftUI での命名 00:58:36 クロージング 00:59:02 談笑タイム 00:59:11 SwiftUI の accesibility hint の謎 01:03:03 SwiftUI 独特の文化? 01:08:34 ガイドラインは、あくまでもガイドライン 01:09:35 Array.randomElement 01:13:07 API デザインガイドラインに SwiftUI は則ってなさそう 01:14:27 既存の文化は大切にする 01:16:31 flatMap と compactMap 01:18:46 DSL 01:24:19 ガイドラインに則るのも難しい 01:25:54 クロージング —————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #14
はい、じゃあそろそろ始めていきましょうか。今回はここからです。前回はプロトコル名の名前の付け方について話しましたね。プロトコルの名前を付ける際、付属型として使用されるかどうかに関わらず、名前が名詞の場合はそのまま名詞にしておくと良い感じになるよというお話でしたが、これは個人的にとても良いと思いましたので、改めて紹介しておきますね。
次に進みます。ここから新しい話題に入りますが、前回少し話した内容でもあります。意外と難しいところですが、自由度のある型情報に名前を付けてあげることについてです。今回はAPIデザインガイドラインの中のAPIの名前の付け方を見ていきますが、それを明瞭に使っていけるようにするための方法として、この点が挙げられています。
重要なポイントとして自由度のある型情報とは、例えばInt
やString
、NSObject
、AnyObject
、さらには純粋なAny
などがあります。他にもタプル型などがそうですが、これらは確かに役割を持っていますが、その役割が非常に基礎的な型情報といったものを指します。こういった型情報を扱うときには、APIの明瞭性、つまり前回話した「過不足なくAPIを表現する」というルールだけでは不足してしまう場合があります。情報が落ちてしまうことがあるのです。そのため、これを補完してあげる必要があるというのが今回のポイントです。
そのあたりを説明しても何が情報として落ちるのかよく分からないかもしれません。なので、例を見ながら理解を深めていきましょう。まずはダメな例、不明瞭となる例からです。例えば以下のようなAPI定義があります:
func addObserver(_ observer: NSObject, forKeyPath keyPath: String)
これを見るだけではそんなに不明瞭ではない感じがするかもしれません。実際にコードとして書いたほうがわかりやすいですね。
func addObserver(_ observer: NSObject, forKeyPath keyPath: String)
このAPIがあったとして、これはFoundation
のものかな。これだけ見ると結構綺麗に書けているし、読めると思うんですよ。たとえば以下のように使うことを考えてみましょう。
grid.addObserver(self, forKeyPath: "graphics")
このコードでは、グリッドにセルフをグラフィックスというキーの値として追加しているのですが、これを見ても何を目的としてセルフを追加しているのか、またforKeyPath
として「グラフィックス」とは何を意味しているのか、という情報がこの1行では全然読み取れなくなってしまうのです。
これに対して、以下のように書かれているとします:
func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?)
このコードを使うときには、
grid.addObserver(self, forKeyPath: "graphics", options: .new, context: nil)
このように詳細に書かれていれば、セルフをオブザーバーとして追加し、keyPath
として「グラフィックス」という値を監視するということが1行で読み取れるようになります。これがとても大事なポイントです。
APIを定義する際、特にaddObserver(_ observer: NSObject, forKeyPath keyPath: String)
のように書くと冗長に感じることもありますが、この冗長性が使われる際の明瞭性につながります。そして、実際に使う側のコードも明瞭に表現できるため、API全体で見ると過不足なく表現できていることになります。
コメントを書くときにも、この冗長性が役立ちます。例えば、以下のように書くと、
/// 監視対象オブジェクトに対してオブザーバを追加します。
/// - Parameters:
/// - observer: 追加するオブザーバ
/// - keyPath: 監視するプロパティのキー
/// - options: 監視オプション
/// - context: 任意のポインタ
func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?)
このようにドキュメントコメントで説明を書けば、APIが宣伝されるだけでなく、正しく綺麗に表現できるようになります。宣言としても実際使うときも、セルフが何なのか、グラフィックスが何なのかという情報が明瞭に表現できてきます。
このような形で、とても上手くいきます。 ここで Observer について話を進めるときに、型情報をどのように説明するかを考えてみます。例えば、NSオブジェクト
でない場合、どのような雰囲気になるのかです。キーパスについても考えますが、まずはObserverが重要です。
例えば、プロトコルで Observer
を作成したとします。その場合、何かしらのクラスを作成する必要があります。Observerはクラスに縛った方が自然かもしれません。したがって、クラスを作成してみましょう。何を監視するか、ファイルを例にとってみます。例えば、FileObserver
やFileSystemObserver
といった名前ですね。
class FileSystemObserver {
// クラスの定義
}
このようにして FileSystemObserver
クラスを作成したとします。次に、変数としてインスタンスを用意するとしましょう。monitor
という名前にしてみます。しかし、Observer
が入るとリーダビリティや役割が曖昧になりがちです。そこで、以下のように addObserver
メソッドを含むクラスを定義します。
class FileSystemObserver {
func addObserver(_ observer: Any) {
// observer を追加する処理
}
}
次に KeyPath
を使用する例を挙げてみます。例えば、graphics
というキーを持つとします。
observer.addObserver(self, forKeyPath: "graphics")
AnyObject
を想定する場合、具体的な型が複数存在する可能性があり、それに伴って変数名も多岐に渡ることになります。NSオブジェクト
の場合、例えば NSViewController
や NSFileManager
、NSUserDefaults
などです。これらはすべて NSObject
を継承しているため、多様な型が存在することになります。
型制約が緩すぎる場合、変数名の意味が不明瞭になる傾向があります。逆に、特化した型制約を設けると、役割が明確になります。例えば、監視対象を指す場合、変数名が 何々Observer
や 何々Monitor
、何々Handler
などといった名前に限定されることが期待できます。
そのため、メソッド名 addObserver
は冗長に感じるかもしれませんが、変数名によって役割が明確になるため重要です。そして、KeyPath
のように具体的な情報を伴う型制約があると、取りうる値の範囲が限定されるため、理解しやすくなります。
たとえば、KeyPath
を使う場合、以下のようにすると具体性が増します。
let keyPath = "graphicPath"
observer.addObserver(self, forKeyPath: keyPath)
まとめると、型の厳密さと変数名の命名は密接に関連しており、コードの可読性と明確さに大きな影響を与えます。特に Observer
のような概念は、その役割が明確であることが重要です。
このように、話を進めると解釈が難しくなる部分もありますが、基本的な概念として型の制約と変数名の関係性をしっかり理解することが大切です。 ただし、ここがね、KeyPath型だったとすると、何のKeyPathにしようかな。これでいいのか。ジェネリクスやパーシャルKeyPathでしょ。KeyPathの種類が分からなくなってきた。あ、いいKeyPathがあるじゃないですか。これでいいかな。KeyPathに制限をつけると、取り得るKeyPathっていうのは当然ながら限られてきます。
例えば、String
のcount
をKeyPathに設定するとします。こうすると、変数名からKeyPath情報がちょっと曖昧になって、APIの利用方法が分かりにくくなるかもしれません。でも、ここが取れるものがKeyPathに限られるから、例えばカウントパスにしても良いかもしれません。こういうふうにね。喋っていてよく分からなくなってきましたが、こんな感じです。
エラー無くしてみようか。このようにすると、グリッドに対するオブザーバーが絶対にKeyPathしか取らないので、APIを見る限りは正確に把握できます。右側のユーティリティエリアに、クイックヘルプインスペクターがあります。プレイグラウンドの際にも便利なので、行を変えるとカーソルの位置にあるものがリアルタイムで分かります。これは便利ですよね。型が明確であれば問題なく伝わります。
ここまでの話で何かおかしい点があれば、コメントや声で教えてくれると助かります。ただ、使ったラベルが「forKeyPath」ではなく、「KeyPath」です。「forKeyPath」が適切ですね。こんな感じで、KeyPathの型情報がしっかりしていれば、クリアなコードになります。
例えば、addObserver
でself
に対するKeyPath
としてグラフィックスを指定する場合、定義を読まなくても何をやっているのか分かるはずです。FileObserver
やcountKeyPath
という名前がついていれば、使い方が分かりやすいでしょう。
例えば、addObserver(self, forKeyPath: "graphics")
というコードだけを読んで、その文が何をしているのかが分かることが重要です。self
が何であるか、グラフィックスが何を指すのかが明確でない場合があります。グリッドに対するオブザーバーとしてself
を追加し、KeyPathがグラフィックスであることを表しています。
ユーザーデフォルトもforKey
になっています。何か中途半端なものがあったような気がしますが、それはおいおい。withUnsafeBitCast
も使い方がself
を書いている時点で型が分かりますね。同様に、ユニットテストでも自然に読める気がします。
総じて、KeyPathをしっかり指定することが重要で、特にforKeyPath
が明確な場合は、利用者がコードを読んで理解しやすくなります。こういった考え方はSwiftのコードを書く上でも役立つと思います。 なるほどですね、結構曖昧にやっていたことが自分でも曖昧にやっていたことがよく分かりました。すみません、質問いいですか?
どうぞ。
このスライドで、addObserver
で仮引数名がアンダースコアで、実引数が observer
というのと、add
で仮引数名がアンダースコア、実引数が observer
の違いは分かりました。ただ、使った後というか、使われたときに第一引数が何なのかというのがすぐに分からないため、observer
をつけなきゃなというのは分かったんです。ですが、コメントで自分が13時30分にやった例で、addObserver
で仮引数名が _
で実引数が observer
というのと、実引数名がないパターン、つまりadd
で仮引数名なしで単純に実引数名が observer
というパターンがありましたが、それだと違いがあまりないように思えて、使うときも使った後も observer
が示されている気がします。なぜ Apple は今中井さんが書かれている方を採用したのかなと思います。これは完全に英語の例になるんですが、アドの方なのか、アッドなのかの違いは、言葉にするのはすごく難しいのですが、両方のパターンがあるはずです。確か Apple の公式の中でも両方あると思いますが、主体としてあくまで追加をするのか、それとも foge
を追加するのかの違いがあります。
英語の語感的に addFoge
となると、あくまでこのメソッドは addFoge
するよという話になります。他に foge
ではなく fuga
も追加するようなメソッドがあったときは、後者を選ばれがちというイメージです。addFogeFogeFugaFuga
のほうが、たぶん add
のほうが引数として foge
と fuga
は同じレイヤーにあるものです。addFoge
はあくまで主体は add
する対象は foge
であって、それ以降の引数は addFoge
に対する追加の情報になります。なので addObserver
だと、あくまで追加する主体が observer
であり、keyPath
はその observer
に対する補足情報となります。だから addObserver
ですね。
例としては addFogeFogeFugaFuga
があります。引数が2つ、 foge
と fuga
の場合、自分はこのイメージで言いました。このイメージ、両方ともあり得ると思います。あまり違いはないかと思います。ですので add
のほうが良いかもしれません。例えば addFoge
、アンダースコア foge
、fuga
ですね。これだとちょっとおかしいですね。
意外と難しいですね。今、こうやって並べてもらって思ったんですが、add
するものが foge
だったり fuga
だったりする場合は後の方がしっくり来るんでしょうね。あと、これをオーバーロードって言います。実装方法として、同じ関数名を使う場合です。ラベル名が変わっているからオーバーロードじゃないか、オーバーロードは API 名が同じ場合ですね。例えばラベルがない場合はオーバーロードになります。同じことをやっているというのが暗黙的に表現できる手法ですね。この上の2つは API が違うのでオーバーロードではなく、それぞれ foge
を追加するメソッドと fuga
を追加するメソッドになっています。
Apple はやっぱり上を採ったというだけかもしれないですね。他の例で、例えば色を作るときは間違いなく最初の引数ラベルがあります。例えば色を作るとき、赤、緑、青が3つ全部ラベルに入っているはずです。これはイニシャライザーでしょうか、イニシャライザーでもファクトリーメソッドでもあります。イニシャライザーの場合は何が外にないといけないかが重要です。
メソッド makeWidget
で foge
と fuga
がパラメータ要素になります。このとき、メイク foge
と fuge
の順番でやり方が違います。これについて話しました。そのため、ファクトリーメソッドの場合は make
としてはっきり示される必要があります。
この辺りも、いろんなルールがあり、自分の中で整理できました。前置詞は括弧の中にラベルでつけます。例えば makeName
という取り方をするか、name
だけでなく属性的な要素を示す場合も考慮されます。
おそらく Apple がこうしましょうねと決めているような気もしますが、実際の使用例で makeTitle
という書き方もあります。具体例を挙げると、たとえば String
のイニシャライザーの場合は、名前を明記する必要があるため、そこが異なる点です。 とりあえず、メソッド名に関しては、APIデザインガイドラインに従うことが重要です。ガイドラインには、「全知詞をラベルとして入れる」というルールがあります。そのため、複数の選択肢がある場合でも、統一するために上の方にラベルを統一することが推奨されます。具体的には、配列操作のメソッドremove(at:)
も全知詞を含むためラベルとして付けるのがルールです。ただし、二次元配列など特殊な場合には、remove(atX:Y:)
とするのではなく、remove(at:)
として全知詞を外に出します。
オブジェクティブCの影響もあるかもしれません。例えば、メソッド名にadd(hoge:fuga:)
といったメソッド名が出てきます。オブジェクティブCの文法では、こうしたラベルとして全知詞を活用していました。Swiftでもこのようなスタイルが受け継がれています。
addObserver
メソッドなど、前の議論で説明されていた通り、キーパスを使用するメソッドに関しても同様のルールが適用されます。全知詞を含むラベルを使用することで、コードがより読みやすくなり、何をしているのかが明確になります。
ズレが生じる場合もあり、自分の理解や他のチームメンバーとの共有は大事です。例えば、SwiftUIを含んだ標準的なライブラリを見てみると、引数にラベルが含まれることが多く、それがどのように役立つかを発見することができます。
最終的に、理解を深めるために、ガイドラインに従いながら自分でも試してみたり、チームで共有することが大切だと感じました。ぜひ、今後も勉強会やOJチャンネルで情報を共有し合い、理解を深めていければと思います。 フィクストサイズ、フィクストサイズ括弧、フォライゾンタル、バーチカルとかの指定があります。フィクストサイズ、フォライゾンタル括弧、バーチカルではなくて、フィクストサイズ括弧、フォライゾンタル、バーチカルでフレームウィス括弧、アンストハイトではなくて、フレーム括弧、ウィス、ハイトになっています。
なるほど、基本的にはこのメソッドは何をやっているかに対して引数のラベルはやろうとしていることに対しての拘束情報というか、面白いですね。SwiftUIのAPI、昔の発想だとwith
とか入れそうですけどね。
そうですね。ちょっと見てみましょう。オフセット
括弧X, Y
ですね。オフセットX
括弧アンダースコアY
ではなくて。なので、根本的にはメソッド名はこれは何をやっているか、引数ラベルはやっていることに対しての拘束情報の方が多分自然かなという感じです。
なるほど、確かに今表示中のものが不倫落ちない部分があります。以前のAPIデザインガイドラインで話した内容と今のSwiftUIの雰囲気が何か違う気がするんですよ。もう少しそれも踏まえつつ、ガイドラインを読みつつ、SwiftUIのAPIを読みつつ進めてみたいですね。みんなもそんな感じのつもりで。
例えば、アクセシビリティ情報をビューに追加するときには、accessibility(hint:)
とaccessibilityHint
括弧アンダースコアhint
があります。これらはそれぞれヒントの型が違います。どこで実際に見れるのかというと、適当にテキストビューを作成して、.accessibility
と入力するとaccessibility(hint:)
とaccessibilityHint
があります。ラベルにヒントが入っている場合は型名もヒントですし、逆にaccessibilityHint
の場合はテキスト型ということですね。
アクセシビリティヒントのやることは一緒です。上のヒントはヒント型、下のはストリング型です。上の方がヒント型なので.init
を使い、下はそのまま文字列を渡します。これを意識して使い分けるのは結構難しいですし、ガイドラインからどう分かれているのかが自分の中ではまだ分かっていないところです。
アクセシビリティはiOSでは目が不自由な人向けの操作を支援するためのもので、ビューがハイライトされたときにiPhoneがその要素を読み上げる機能です。Swiftのアクセシビリティメソッドチェーンは、アニメーションを追加するときのように.animation
の感覚で付ける感じです。
ガイドラインに従うと、全てaccessibility
にするわけにはいかないので、情報としてノイズになる可能性があるし、新しいビューを作って返す内部処理でも、実際にビューを作らない場合もあり得ます。プロパティを変更して返す場合もあります。
例えば、Demystify SwiftUIのセッションでも言及されていましたが、アニメーションを作成する際に、ビューのIDを同じにしないといけない場合もあるので、その場合プロパティによってビューを変えたいときにはビューのIDを保持するために場合分けをしています。
この記法は指示をしているというよりも、プロパティを変更している感覚です。実際に内部処理で違うオブジェクトを返しているかどうかは実装上の問題なので、気にしなくて良いでしょう。その実装を気にしてしまうと問題ですね。
あと、アクセシビリティヒントのメソッドについてですが、ヒントというラベルがディプレケーテッド(非推奨)になっているので、おそらく名前が変わったのではないかと思います。 将来デプレケーションになる可能性がある。実際に Swift 5.5 で無くなっています。ちゃんとリネームで書いてありますね。でも必ずしもそうとは限らないかもしれません。まだデプレケートになっていないかもしれませんね。少しリアルな話をしますが、名前が変わったんじゃないかなと思います。「accessibilityHint
」というメソッド名が本来の姿な気がしますが、どうでしょうか。
実際に定義を辿ってみると、これは「accessibilityHint
」にリネームされたと書いてありますね。確かに、これは適当に書かれているようですが、将来このAPIが使えなくなるという話だと思います。現段階では警告が出ていませんが、いずれ警告が出るようになるでしょう。潔く名前を変えるのが良いとは思いますが、まだ議論中なのかもしれません。
SwiftUIはAPIデザインガイドラインに必ずしも従っていない可能性もあります。例えば、「map
」、「compactMap
」、「filter
」のような関数はガイドラインに反している場合がありますが、プログラミングの慣習や既存の文化が取り入れられているのだと思います。
個人的に感じるのは、「randomElement
」という名称が少し冗長ではないかということです。例えば、整数型のランダムな値を取得する際には「random()
」を使用しますが、同様に配列からランダムな要素を取得する場合も「random()
」で良いのではないかと思います。「randomElement
」とすることで、配列の中のどれかの要素をランダムに返すことが明確になるとも言えますが、戻り値が要素である時点でそれは明らかです。
APIデザインガイドラインはあくまでガイドラインであり、必ず守る必要はないとも言えます。例えば、「add
」や「adding
」といった手続き型の命名は避けられる場合があります。また、関数型プログラミングの話に関連して、存在する文化に従うという方針があります。英語表現や既存の関数名を変えたりすることは避けます。
総じて言えるのは、例えば「map
」や「reviews
」といった既存の文化があるため、その名前を変更しない方針が取られているということです。そして、SwiftUIの宣言的なプログラミングにはまだ明確なガイドラインが定義されていない可能性があります。これらはガイドラインに厳密に従っていない例として挙げられます。
また、時々奇妙な変更もあります。例えば「flatMap
」から「compactMap
」に変更されましたが、これは関数型プログラミングの純粋な「flatMap
」を維持するための変更です。もともと両方とも「flatMap
」とされていたものが、関数型言語向けの「flatMap
」に一本化されました。
そのため、API名称の変更については、今後も議論や変更が続く可能性がありますが、基本的には既存の文化やガイドラインに沿った形でアップデートされていくと思います。 そういうことなんですね。二重配列と一重配列の動きについてですが、元々は二重配列と一重配列にするだけでなく、配列内の「nill」をすべて取り除くという動作も「flatMap」だったんですよ。それで、この「nill」を取り除くのを「compactMap」に切り出したわけですね。なるほど、「flatMap」で「compactMap」的なことをやるのは結構大変だったんですよね。私はその時代を知らなかったんですけど。
文脈がわかりにくいのですが、「nill」を取り除きたいのが二重配列と一重配列にしたい場合のことですね。これが出てきたから、これはガイドラインとして間違いないんですね。元々「flatMap」の動作を整理したのが正しいということです。なるほど、「flatMap」がやりたいのであれば二重配列で、三重でも四重でも、配列が二重や三重になると「flatMap」になります。逆に三重の「nill」だったら「compactMap」になります。元々は三重の「nill」でも「flatMap」でした。
あと、「resultBuilder」があって、これの大事なコンセプトとして「独自の表現」を変換するというものがあります。宣言的に変換するという意味合いですね。専門的な言葉があったと思うんですけど、こういう風な書き方をするんですよ。これは普通の書き方だと色々な書き方がありますけど、「result +=」のように手続き型を宣言型に変えるという役割を持っています。
「resultBuilder」はどっちも同じ動きをするはずです。この後、ジョインしなきゃいけない。このメソッドの中のブロックを専門的な表現方法に再構築するという、Swift言語の構文を独自の表現方法に再構築する仕組みが「resultBuilder」としてあります。そうすると、ここはSwiftであってSwiftにあらず、みたいな世界観がここの中に存在するという価値観があるんです。つまり、SwiftUIってこういう価値観じゃないですか。そうなりますね。SwiftUI独自の言語体系というものを持って、それで理由を作っていくという。
SwiftUIを考えると、もしかするとここはSwiftであってSwiftにあらず、要はSwiftUIはSwiftにあってSwiftにあらずと解釈すると、APIデザインガイドラインも標準的なものではなくて、SwiftUI APIデザインガイドラインみたいなものが生まれてもおかしくありませんね。確かに、普通に言うとSwiftでコードを書いている時に「resultBuilder」を使おうと思わないんですよね。あまりにも書き方が普通のSwiftとは掛け離れていて。そうなんですよ。だから掛け離れすぎて、何を書いているのか分からなくなるからやめようという発想になってくると思うんですが、それを許容するとまた色々世界観が変わってきます。
時々使ったりするんですが、自分は何で使ったかな。とりあえず簡単なところとしたら文字列の連結なんか便利ですよね。一番役に立つ使い方なら、SwiftUIと同じようなUI構築、HTMLを書く時とか、そういった風に何かコンポーネントを組み合わせる時に宣言的UI、宣言的に何かをやりたい時に使います。宣言的にとらわれなくても色々あった気がしますが、実質宣言的っぽくなりますね。色々とこういう使い方が面白いんじゃないかと思ったことがあったので、それを見つけてOJTに書こうかな。
そう考えると、SwiftUI APIデザインガイドラインを読んでいくのは意外と難しいですね。SwiftUIを参考にするとおかしくなる可能性もあります。まだはっきりとは言えませんが、Objective-Cが混ざると雰囲気が違うし、Swift標準ライブラリーを見ても「randomElement」って何だという感じになるしね。
この発想好きだな。「random」で出てくる自分が思いついた発想も好きなんですが、どう考えてもね、誰が考えるんだという。でもガイドラインだから、そういうものなんでしょうね。強制ルールではないですが、迷った時には指標となって迷子になりにくくなる大事なポイントでもあるので、一通り基本ルールみたいなのは抑える、そんな気持ちで聞くといいかもしれませんね。
よかった、あとは自分の中の課題としてこの1ページが何を言っているのか分からなくなったところと、結構質問や話題の中で色々なことが出てきて、その後に色々なところで話題に上がってきたことも出てくるんですけど、それは飛ばさずに読み進めていこうと思います。じゃあ、とりあえずこんなところですかね。
はい、30分過ぎたので、他に何かあったらまたの機会にOJTに書いてもらったり、またこういう時間ができた時に話す時間を作ってもいいかもしれません。
では、今日は1時間半と結構長くなりましたけど、これで終わりにしましょう。ありがとうございました。