nunulkのプログラミング徒然日記

毎日暇なのでプログラミングの話題について書きたいことがあれば書いていきます。

技術の学習コストと学習ハードルについて

はじめに

この記事について

こちらの記事を読んで、学習コストと学習ハードルの違いについて考えてみました。

積極的な技術選定と消極的な技術選定 - uhyo/blog

「学習コストが高い」という言い方だと、積極的な技術選定なのか消極的な技術選定なのかを明確にできていないので、技術選定理由の説明としてはあまり良くありません。素直に「学習ハードル」や「難易度」といった方が明確になるでしょう。

最初に読んだときはなぜ「学習コスト」がだめで「学習ハードル」がいいのかがよくわからなかったんですが、少し考えて理解できた気がしたので書き残しておきます。

上記記事における学習コストと学習ハードル

このことから、技術の「学習コスト」よりも、むしろ難易度、すなわち「学習ハードル」のほうが本質的なのではないかと思います。学習ハードルは純粋に技術の性質として扱えますが、学習コストは誰が学習するかによって変わるため、技術的な要因と非技術的な要因が混ざった概念です。

自分が読んだ限りでは学習コストと学習ハードルの明確な違いが記述されている箇所は見つけられませんでしたが、学習コストは学習ハードルと学習者の学習速度や効率によって決まるもの、と解釈しました。

let x = 学習ハードル;
let y = 学習者の学習速度;
let z = x * y;
// Z(学習コスト) = X(学習ハードル) x Y(学習者の学習速度)

そう考えると筆者の言わんとすることが理解できる気がします。

ある技術が持つ複雑性を仮に数値化できるとして、それを係数として、学習者に応じたパラメータを掛け合せることで学習コストが算出できる、ということだとすると合点がいきます。

学習ハードルが問題になるケース

ここからは余談です。

自分の好きな言語のひとつに Clojure があります。ClojureJava の資産が活用できる関数型言語で、Java に比べると圧倒的に少ないコードで書けるし、副作用や状態をロジックと分離できるため不具合も少なく書けるのが好きです。しかし、LISP 方言なので S 式で記述する必要があり、ポピュラーな C 言語由来の文法とはまったく違うため、学習ハードルは高いです。個人的に S 式にまったく抵抗感はなかったですが、拒絶反応を示す人もいるだろうなというのは容易に想像できます(なにせ既知の言語とは文法が違いすぎる)。

わりと最近(といっても3年前くらいか) ClojureScript で書かれたフロントエンドを JavaScript で置き換えている話(サイボウズだったかな)を読みましたが、人が増えるにつれて既存の技術が持つ学習ハードルの高さに比例して学習コストが増えてしまうので、この判断はやむを得ないだろうな、という感想を持ちました。

あとは、Scala みたいに複雑な文法(キーワードの数が多い1、カスタム演算子が作れる、ケースクラス、トレイト、etc.)を持つ言語も学習ハードルは高いと言えるでしょう。個人の感想ですが、HaskellOCaml もそうですが、関数型言語は学習ハードル高めなのが多い気がします(ただ、Clojure はわりとすんなり馴染んだのが不思議です)。

学習コストが問題になるケース

上の話と繋がりますが、頻繁に人が入れ替わる、たくさんの人が入ってくる、みたいなケースで問題になりそうです。

仮に新しくチームに加入するメンバーが年間に5人いるとすると、5人中何人がその技術に明るいかというのは学習コストに影響します。

/* チームA、Bそれぞれ5人ずつの学習コストを計算する
技術Aはハードル10
技術Bはハードル5
*/
const HURDLE_TECH_A: usize = 10;
const HURDLE_TECH_B: usize = 5;
/*
チームAにおいて技術Aに明るいメンバーは1人(小さいほど速く学習が終わる、学習済みなら 0)
チームBにおいて技術Bに明るいメンバーは4人(小さいほど速く学習が終わる、学習済みなら 0)
*/
let velocities_team_a = vec![0, 10, 10, 10, 10];
let velocities_team_b = vec![10, 0, 0, 0, 0];
let cost_team_a = velocities_team_a.iter().map(|x| x * HURDLE_TECH_A).sum::<usize>();
let cost_team_b = velocities_team_b.iter().map(|x| x * HURDLE_TECH_B).sum::<usize>();
println!("{:?}", (cost_team_a, cost_team_b));
// チームAの学習コストはチームBの8倍
// (400, 50)

わりと極端な二者ですが、古い技術と新しい技術を比べるとわりとこうなるんじゃないかなとも思っていて、推測ですが、ClojureScript から JavaScript へのリプレイスはこういう事情があったんじゃないかと思います。

Elm も一時期話題になりましたが、最近耳にしないのは話題にならないほどポピュラーになったからなのか、選択肢にならなくなってしまったのか、どっちかはわかりませんが、なんとなく後者な気がしています。

こう考えると学習コストは学習ハードルの高さに比例しますし、チームに加入してくる未学習メンバーの人数にも比例します。

人の出入りが激しいチームの場合、新しめかつ難易度の高い技術を導入するにはリスクが高いと言えるんじゃないかと思いますし、いつだれが辞めるかはだれにも(本人にすら)わからないので、課題に対する技術の適合性(その技術でなければ解決できない度合い)とトータルの学習コストを天秤にかけざるを得ないよなぁ、と思います。

Rust の学習ハードルは高いかどうか

こちらも完全に余談ですが、近年注目を浴びる Rust もよく学習ハードル(学習コスト)が高いと言われていて、最近自分も触り始めたのでその点にも触れておきます。

メモリ管理手法が従来のプログラミング言語とは異なるので、この一点で Rust の学習ハードルは高いと言えるでしょう。所有権、借用、ライフタイムあたりが難しい、という意見はちらほら見かけましたし、自分もそう感じます。

しかし、ガベージコレクションによってメモリ管理する言語ではどうしても開放時のコストが高くなりますし、逆にプログラマにメモリ管理を任せるのも危険だという懸念もわかります(自分の母語は C なので、その辺の難しさはわかります)。そういった意味で、Rust の方法は両者のいいとこ取り(開放は自動で行われる、ただし正しく開放されるようにプログラマが管理する)とも言えるかもしれず、これを今後誕生する言語が取り入れて主流になる可能性はありますが、ガベージコレクション時のリソースコストとメモリ管理をプログラマが行うコストを天秤にかけると、そこまでリソースコスト管理をシビアにやらないといけないシステムやサービスがあるかというと、どうなんでしょうね。その観点から Go で書かれたシステムを Rust で置き換えたっていう話もどこかで読みましたが、自分は Go はまったくの門外漢ですが、Java のようにあるていど GC のコントロールができるのであればそっちの道もあるわけで、悩ましいところです。

おわりに

とくにまとめとかはないですが、学習好き・得意なメンバーを集めて新しい技術をガンガン取り入れるか、枯れたポピュラーな技術だけを使うか、という両極でいうと、自分は後者寄りですね、Clojure や Elixir を使ったシステムは作りたいですが、賛同者を得る労力と可能性を考えるとあまりポジティブにはなれないので。


  1. 実は意外と多くなくこちらのページによると40個と決して多くはないんですが、言語仕様がシンプルと評される Go の25個と比較すると少ないので