実はオブジェクト指向ってしっくりこないんです!
私はJavaでキャリアを始めたので、当然、オブジェクト指向を前提としてプログラミングを学んでいきました。オブジェクト指向の概念を聞いたとき、なるほどこれはよくできているなと思ったのを覚えています。オブジェクト指向では、現実世界の「もの」をそのままオブジェクトに表現します。なるほど、合理的でプログラミングが簡単になるように感じます。ちょうど現実のものを操作するようにプログラミングができるのですね。
実際にオブジェクト指向でプログラムを書こうとして分かったのは、私が作っているのはコンピューターのコードであって、現実のものではなかったということです。ArrayList
って現実の何に対応するんでしょうか? 本棚?
「プログラミングはデータの入出力と、その変形のことだ」というデータ指向プログラミングの考えを知ったことが、決定的にオブジェクト指向への興味を失わせ、関数型プログラミングを勉強する契機になりました。
staticおじさんが『実はオブジェクト指向ってしっくりこないんです!』を書いたのは2010年、もう8年も昔の話です。その記事の内容もさることながら学歴差別などで無事炎上したわけですが、プログラミングに関する部分に関しては、あらためてその文意をくみ取って読んでいくと、必ずしも一方的に馬鹿にするようなことではないと思うようになりました。特にコメント欄には的を外している反論も多く、一方的にstaticおじさんを悪者にする気にはなれません(学歴差別や脅しは論外ですが)。読み返してみて、オブジェクト指向が、あまりにも神格化されているように感じたのです。
staticおじさんの逆襲
時間がたち、staticおじさんの存在も風化しつつあります。そろそろstaticおじさんを再び検証する時期でないでしょうか。
(なお明確にしておきますが、この記事はstaticおじさんや当時の議論を行っていた人を馬鹿にしたり、あげつらったりするものでなく、プログラミング・技術という点から検討しなおそうというものです)
「共有変数も、pubulic static宣言していまう。したがってプロパティなんて作らない。」(原文ママ)
グローバル変数をpublicにするのはかなりやばそうですし、「したがって」のつながりもよくわかりません。ですが「プロパティなんて作らない」というのは必ずしも間違いとは言えないのではないでしょうか?
Javaではgetter/setterは害悪だという議論が、昔から盛んでした。私はC#については詳しくないですが、自動実装プロパティが実装されたのはC# 3.0、すなわち2007年からだそうです。元記事が書かれた時点ではまだ新しい機能といっていいでしょう。
public string Name { get; set; }
つまりそれ以前はJava同様、getter/setterを書いていたわけです。getter/setterの是非に関する議論はここではしませんが、プロパティを作らないからと言って必ずしも悪いということはないでしょう。パブリックなフィールドを使う利点は確かにあります。
「オブジェクト指向の入門書では、クラスが持つ隠ぺい性が強調されているが、これは他の言語でもローカル変数であれば隠ぺいできる話。」
これは全く正当だと思われます。カプセル化はオブジェクト指向と結びついた思想ですが、スコープの制御はオブジェクト指向に固有のものではありません。JavaScriptにはIIFEという、まさに「ローカル変数であれば隠蔽できる」ことを利用したテクニックがあります(というかそもそもvar
のスコープがおかしいのです)。逆にPythonはオブジェクト指向言語ですが、privateなスコープは存在しません。
(function() {
var message = "I have a bad feeling about this"
console.log(message)
})()
「Visual Studioは「インスタンス名.」と打てばプロパティやメンバー関数の候補が出てくるから楽になった。」
よくIDEを使うのは堕落している証拠だといって糾弾する人がいますが、私はIDE肯定派です。IntelliJを使っていると、Ctrl+SpaceとEnterだけでプログラムが完成します。そしてそれは良いことです。「エクセルマクロを使うとズルしているといわれて怒られる」というインターネットジョークがありますが、ズルをして納期が早くなるなら結構なことではないですか?
冗談はともかく、IDEは便利です。入力補完、リファクタリング補助、自動生成、さまざまなツールとの統合。「入力補完に頼っているとプログラムが学べない」というのは極端です。配列のサイズを取得するメソッドがlength()
なのかsize()
なのか、それともメソッドではなくlength
フィールドにアクセスするのか覚えることがプログラミングの学習なのでしょうか? 私はそうは思いません。
Vimを使いたいのなら使うのを止めはしません。Vimはいいエディターです。だからと言って、他人がIDEを使うことを否定するのは行き過ぎです。でもemacsはクソ。
「オブジェクト指向は、(...)プログラムのアルゴリズムとは無関係のものである。」
これも間違っていません。アルゴリズムとプログラミング言語のパラダイムは完全に無関係ではないにしても、取り立て叩くような発言ではないでしょう。
もちろん言語によってはアルゴリズムと深く結びついているものもありますが、オブジェクト指向とアルゴリズムの本質ではありません。
「非オブジェクト指向言語で数値型宣言されているものは + (プラス)という記号を使って加算できるが、文字列宣言されているものは加算できない。プラスという記号が一種のメンバー関数の役割を果たしている。」
前半部分は何を言っているのかよくわかりません。+記号と構文と言語の規則の問題であり、加算うんぬんは関係ありません。恐らくCを念頭に置いた発言かと思います。
しかし後半部分の「プラスという記号が一種のメンバー関数の役割を果たしている。」は示唆に富んだ指摘です。プログラミング言語によっては+
を単なる関数として扱い、特別な規則を追加しない場合もあります。
例えばClojureでは以下のようになります。
user=> (source +)
(defn +
"Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'"
{:inline (nary-inline 'add 'unchecked_add)
:inline-arities >1?
:added "1.2"}
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce1 + (+ x y) more)))
nil
Clojureでは+
は単なる関数でしかありません。Kotlinでは演算子オーバーロードを利用して、+
記号をplus()
メソッドの呼び出しとみなすことができます。Scalaでは+
も数値クラスのメソッドの一つであり、中置記法で呼び出しているのと同義です。
コメント欄の指摘
コメント欄におけるstaticおじさんの発言、およびそれに対する批判コメントについても、検討していきたいと思います。
「ポリフォーフィズム」(ママ)
簡単なリサーチをして、ポリモーフィズムの意味を調べてみました。
poly- [連結要素]多数の、多量の(↔mono-)
morph (…が[を])〔…に〕変身する[させる]
(ジーニアス英和辞典より)
まさにあるインターフェースが、多数のクラスの間で変身するというわけですね。
StackOverflowによれば、ポリモーフィズムはもともと生物学の用語で、同じ種が異なった形態をとることをいう語だそうです。代表的な例がヒトの血液型です。ということはヒトインターフェースをA型クラスとB型クラスで継承するのは正しかったのですね。
化学においては、「ある化学物質が、同一の化学組成であるにもかかわらず、複数の異なる結晶形を取る現象のこと」を意味しているそうです。
なるほど、インターフェースとクラスの関係をpolymorphismとは、上手く名付けたものです。
staticおじさんの言っている「ポリリズム」はある曲の中で異なったリズムが使われることを意味する音楽用語です。Perfumeの曲『ポリリズム』は途中で5拍子になりますよね。同じものの中に異なった様態が混在するという意味では、Perfumeの登場でポリモーフィズムが流行ったという主張も、必ずしも誤りとは言えない……というのはさすがに無理がありますか。
これは強調しておくべきだと思いますが、ポリモーフィズムはオブジェクト指向に固有の概念ではありません。Clojureにはランタイムポリモーフィズムをサポートするための強力な機能があります。実際マルチメソッドはディスパッチの完全な制御を提供するという点で、オブジェクト指向言語よりもずっと柔軟です(むしろ柔軟すぎて逆に危険なほどです)。仮想関数だけが、多重ディスパッチではないのです。
「.NET Frameworkがオブジェクト指向で作られてる理由」
これは大変興味深い主張です。私は.NETは様々なプログラミング言語を共通の基盤で実行するためのものだと思っていました。様々なオブジェクト指向プログラミング言語を共通の基盤で実行するためのものだったのでしょうか? 少なくともF#という実例がある以上、.NETがオブジェクト指向だけのものではないことは確かです。WikipediaによるとF#ができたのは2005年だそうですから、staticおじさんの時代にはすでにあったことになります。
恥ずかしながら知らなかったのですが、.NETの共通言語ランタイム(CLR)は、ECMA標準のCLI(Common Language Infrastructure)の実装だそうです(参考)。仕様書には下記のような記述がありました。
Because the CTS supports Object-Oriented Programming (OOP) as well as functional and
procedural programming languagesCTS(CLIにおける型システム、Common Type System)は関数型、手続き型言語同様、オブジェクト指向プログラミング(OOP)をサポートするので
(42ページ)
ということは、.NET Frameworkのライブラリーはオブジェクト指向であるにしても、「CLRはオブジェクト指向のために作られているのではない」と言い切ってよさそうです。
Unlike in some OOP languages, in the CTS, two objects that have fundamentally different
representations have different types. Some OOP languages use a different notion of type. They
consider two objects to have the same type if they respond in the same way to the same set of
messages. This notion is captured in the CTS by saying that the objects implement the same
interface.いくつかのOOP言語とは違い、CTSでは、二つの全く異なった表現をもつオブジェクトは、二つの異なった型を持つ。異なった型の概念を使っているOOP言語もある。それらは、同じメッセージのセットに同じやり方で応答する場合、同じ型を持つとみなす。この考えはCTSにおいては、同じインターフェースを実装するオブジェクト、という形で表現される。
(45ページ)
「メッセージ」という言葉を使っています。つまりここで念頭に置かれているのは、メッセージパッシングを使う言語です。
Similarly, some OOP languages (e.g., Smalltalk) consider message passing to be the fundamental
model of computation. In the CTS, this corresponds to calling virtual methods (see §I.8.4.4),
where the signature of the virtual method plays the role of the message.同様に、OOP言語の中には(例えばSmalltalk)、メッセージパッシングを計算の根本的なモデルだと考えるものがある。CTSでは、これは仮想メソッドを呼び出すことに相当し、仮想メソッドのシグネチャがメッセージの役割を果たしている。
これってJavaのような言語が、もともとは抽象的なOOPという概念を実装するにあたって使っている方法と同じですよね。興味深いです。
「そもそも並列処理をシンプルに記述しようとしたら,大域関数だけでコーディングするなんて面倒くさくてできない」
「並行処理をシンプルに記述しようとしたら、大域変数だけでコーディングするなんて面倒くさくてできない」なら意味が通るのですが。関数が素晴らしいのは、呼び出しごとに独立したローカルスコープをもつことであるはずです。オブジェクト指向が可変オブジェクトを利用しようとすることを考えると、並行処理がシンプルになるという主張は奇妙です。名前空間がなければ命名がカオスになるかもしれませんが、やはり名前空間はオブジェクト指向固有の概念ではありません。
「理由があってstaticを使うのではなく、理由が無いからstaticを使う。理由が無い場合は、素直な方法を選ぶものだが、何が標準かというのが違う人なのですね。そこがオブジェクト指向が身についている人と、身についていない人との違いなのでしょう。」
これもまた興味深い指摘です。staticを使わないのが素直な方法なのでしょうか? this
参照を持たないstaticのほうが簡単なように、私には思えます。staticは当然インスタンスの状態に影響を受けませんから、より狭い範囲のことだけを考えてコーディングできます。this
に対する参照が必要な場合だけ、非staticにするのが素直ではないでしょうか。
とはいえ、オブジェクト指向が身についていればstaticを使わない、という主張は理解できます。純粋なオブジェクト指向にstaticは必要ないというのはよく主張されることです。その通りだとは思います。しかし依然としてC#にはstaticがあり、staticを使ってプログラムを書けるのです。
「例えば C# を使っているつもりが、実は C# じゃなかったとか……。」
全くの余談で申し訳ないのですが、私が新卒で入った会社の同期が、「Javaを書いていると思い込んでいたけど実はCだった」と発言して唖然としたことがあります。
「配列要素数が1万個あったら、それぞれコンストラクタで1万回インスタンス生成をしなければならないのか?」
staticおじさんのこの疑問が、そんなに愚かだとは思いません。1万回繰り返すような処理では、パフォーマンスの最適化が必要なケースもあるでしょう。ガベージコレクターがあるからといってメモリーリークが全く起きないわけでないのは、ご承知のとおりです。
これは、メモリ容量のことを心配されての質問でしょうか? それとも、パフォーマンスについての心配でしょうか? 15年前ならこの質問もありかと思いますが、何れにしても今は、その質問自体が無意味です。
私には、無意味と言い切るのは怖いです。重要なのは「1回の処理が」どのくらい重いのかではありません(もちろん場合によってはそうともいえませんが)。この処理が繰り返しであり、計算量が線形で(あるいは階乗なのか、累乗なのか...)増加するということこそ気にするべきです。クヌース先生がO記法を計算機科学に導入したのは、とても偉大な業績なのです。
「だから、オブジェクト指向プログラミングは実践では使えない。データベースを活用したほうが何万件の処理を高速に実行できるという意味でスケーラビリティが全然違うのです。」
データのミューテーションを、SQLを使いDBサーバーでやるのか、アプリケーションサーバー側でやるべきかというのは興味深い問題に思えます。単純に言い切れるようなものではありません。実際の処理に即した慎重なパフォーマンス測定が必要なはずです。私はSQLが苦手な人間なので、これに関して知見のある方がいらっしゃいましたらぜひシェアいただけると嬉しいです。
ところでスケーラビリティーといえば、昨今ではRDBを使うと老害扱いされるという風潮がありましたが、再びRDBへ戻る流れが増えているそうです。結局トランザクションとスキーマは強かったということでしょうか。適材適所と言えばそれまでなのですが。
「スケーラビリティや実行速度が気になるなら分散オブジェクトとか」「オブジェクト指向技術が今後応用されるひとつの道はWEBサービスによる分散オブジェクトを使った企業間取引」
悪いことは言わないから、EJBはやめましょう。グローバル変数のほうがまだましです。CORBAなんて今名前すら出てきませんよね。SOAPももう死にました。RESTもいろいろ批判はあるでしょうが、結局のところ分かったのは、単純なHTTPリクエストで十分な場合がほとんどだったということです。
「読みやすく、保守しやすく、テストしやすい設計・実装にするために、OOPなどの技法を適用するアプローチの方が、品質向上を図れました。」
こんなことを書くとお前もオブジェクト指向をわかっていない、staticおじさんの一味かと叩かれそうですが、私はOOPがテスタビリティー向上につながるとは思っていません。
具体的な例を見ていきたいと思います。例によってJavaのDIフレームワークGuiceのWikiからとってきて少し単純化ものです。ピザのオンライン注文を受け付けるシステムです。
public interface BillingService {
Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}
ピザの注文情報とクレジットカードを表すクラスを受け取り、レシートを返すのですね。早速実装しましょう。
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
ChargeResult result = processor.charge(creditCard, order.getAmount());
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
}
}
注文を処理するためには、クレジットカードを処理するクラスが必要です。実装にはPaypalCreditCardProcessor
クラスを使っていますね。
さて、このクラスを単体テストするにはどうすればいいのでしょうか。このクラスはPaypalCreditCardProcessor
と直接結びつけられています。このままでは、テストのたびに本物のクレジットカードが課金されてしまいます。CIを導入したら大変なことになります。CreditCardProcessor
を外部から設定できるようにして、実装を入れ替えられるようにしましょう。
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
public RealBillingService(CreditCardProcessor processor) {
this.processor = processor;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
ChargeResult result = processor.charge(creditCard, order.getAmount());
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
}
}
これで、テスト時にはモックを使うことで、テストのたびにカードからお金が出ていくことはなくなりました。
このレッスンの教訓は何でしょうか? オブジェクト指向が直接テスタビリティーに向上するわけではないということと、IoCというコンセプトとDIコンテナーが、実際にテスタビリティーを向上させる仕事をしているということです。
こうしたアプローチは言語、あるいはその性格によって異なってくるのは興味深いことだと思います。
PythonにもDIフレームワークはありますが、一般的には直接依存関係を書き換えることが推奨されているようです。Rubyでも同様です。上記の例でいえば、processor
フィールドを、直接書き換えてしまうわけです(この辺批判があればぜひコメント欄へ)。DIコンテナーを使わなくても、柔軟にテストができます。
関数型プログラミングを初めて学んだとき、私が一番疑問に思ったのがテストでした。副作用のない関数は確かに簡単にテストできます。しかし、まさにここで示した例は、どのようにテストするのでしょうか。
恐らくHaskellのような言語では、モナドで副作用を封じ込めるのでしょう。一方LISPのように非純粋な言語では、直接関数を呼び出してしまうことが多いように思えます。Clojureでどのようにテストをするべきか調べていて驚いたのは、with-redefs
を使って、バインディングを書き換えろという答えがされていたことです。
面白いのはこうした手法がJavaでも可能なことです。フィールドをパブリックにしてテスト時に書き換えればいいのです。だから実際にはこうした違いは言語の違い(Javaが冗長な言語?)というよりは、設計方針の違いであり、要するに本番のコードでCreditCardProcessor
をぽんぽん入れ替えるような馬鹿なことを誰かがやらかさないか信じられるかの違いなわけです。いずれにせよ、OOPには関係ありません。
「staticを理解していない人のコードを見ると、いちいちインスタンス宣言しているので笑ってしまう。」
「笑ってしまう」は暴言にしても、「staticは駄目、オブジェクト指向は正しい」と一方的に決めつけるのもまた、間違っているように思えます。
近年人気の言語の多くはトップレベル関数をサポートしてると思います。まさにstaticです。
単なる関数で出来ることをオブジェクトで表現するのは、それがよりよいコードに繋がるからなのでしょうか。それともオブジェクト指向で書くのが正しいと思い込んでいるからオブジェクト指向で書いているのでしょうか。もっと端的に言えば、staticとオブジェクトどちらを使うべきなのでしょうか?
オブジェクト指向を使うと、本当に開発が簡単になるのか?
継承?
extend
すればいいんですよね?あるプロジェクトにて
Effective Javaが繰り返し繰り返し、継承を使用するとき気を付けるよう戒めているのは偶然ではありません。「メソッド呼び出しと異なり、継承はカプセル化を破ります」という忠告を真面目に聞いた人がどれだけいるのでしょうか? 継承を正しく使うのは、とてつもなく難しいのです。ライオンクラスが動物インターフェースを実装して、とかそういう話ではないのです。
結局、ジョシュア・ブロックは、「継承よりコンポジションを選ぶ」(2版項目16)べきだと言っています。
ジェームズ・ゴスリンですら、継承には問題があると言っているのです。
継承よりコンポジションというのは広く言われるようになってきているかと思います。例えばReactは、ミックスインよりも高階コンポーネントを使うよう勧めています。さらに言えば、状態を持つコンポーネントよりも関数型コンポーネントを使うほうがよいとされています。フックを提供するために抽象クラスを継承させるというのはオブジェクト指向では普通に行われることですが、単に高階関数を使うほうが、はるかに簡単で分かりやすい方法です。
別にJavaに限りません。Scalaのトレイトの線形化について、正しく答えられますか? Rubyのミックスインと継承の違いは? 私はRuby案件のために一生懸命暗記しました。今では全く思い出せません。
少なくとも、ロジックを共通化するためだけに継承を使うのは、絶対に間違っています。そして多くの人が、そのような使い方をしているように思います。抽象メソッドを使わず、単に一つの関数にまとめればよいのです。
実際のところ、オブジェクト指向でなければできないことなどないのです。
The Rule of Least Power
わたしに理解できたところはすばらしいし、理解できなかったところもそうだろうと思う。ただし、この書物はだれかデロス島の潜水夫を必要とするね。
ソクラテス
staticおじさんのコメント欄ではオブジェクト指向が業務プログラミングに必須であるかのように言われていますが、そんなことをはありません。オブジェクト指向を批判している人はたくさんいます。一方で私は、この記事のようにオブジェクト指向は全然駄目で関数型プログラミングがすべてを解決するとも思っていません。しかし、オブジェクト指向が当然のように銀の弾丸として扱われるのには、とても違和感を覚えます。
プログラマーというか技術者一般は、何かの問題を自身の技術によって解決するわけです。だから「自分がこんな技術を持っているんだよ」ということを示すために、難しいことを難しくこなそうとする傾向があるように思えます。しかし、本当に難しいのは、難しいことを簡単にすることです。Qiitaのような技術コミュニティーではやはりどうしても難しい技術ばかりが評価されてしまいます。翻ってそれが正しいことなのか一歩立ち止まり考えることが難しくなってきます。
私は文系大学卒で、10行より長いメソッドは読めません。東工大マスターだというstaticおじさんには尊敬の念さえ覚えます。業務経験も未熟です。オブジェクト指向は難しすぎて、実業務で使うのはあきらめました。モナドと言われればライプニッツと答えるような人間です。
だからプログラミングでも、できる限り簡単なやり方をしたいと思っています。難しいとそもそも自分が理解できません。だからstaticおじさんが「勉強不足だ」と言われているのを見ると複雑な気持ちになります。「お前が馬鹿なだけだよ」というのは常に正当な批判ではありますが、staticおじさんを叩いていた人々がどれだけ自分自身の能力を判断していたのか、疑問に思います。オブジェクト指向は簡単? とてもそうは思えません。「果たしてその複雑さを導入する価値があるのか? もっと簡単な方法で実現できないのか?」と思うのです。
難しいものを使うな、もっと簡単なやり方をしろというのは間違っているのでしょうか?
結局のところ、これは勇気の問題なのだと思います。誰かがDockerを使いたいだけのためにDockerを使いたいと言い出した時、「自分がDockerを使えないからDockerを使いたくないと言っている」と思われたくないためにDockerを使うことに賛成する誘惑に抵抗する勇気が必要なのです。
staticおじさんという現象でも、まさに多くの人が「オブジェクト指向は難しい」ということを認めたくなかったのだと思います。だから過剰なまでにオブジェクト指向が持ち上げられることになったのです。しかしオブジェクト指向は難しいのです。デザインパターンは難しいのです。本当に難しいのです。オブジェクト指向の抽象化による利益が、それがもたらす難しさに釣り合っているのでしょうか?
オブジェクト指向が強力な力を持っているのは事実です。しかし強力な力は複雑さをもたらします。強力な機能を使いたいという誘惑に、対抗しなければなりません。
プログラミング言語は複雑さに対処するために、たくさんの機能を持とうとします。しかし、プログラミング言語が強力になればなるほど、逆に言語それ自体がもたらす複雑さが増し、その力を制御できなくなってしまうというパラドックスが生じます。オブジェクト指向の力は本当に必要なものだったのでしょうか?
複雑さはプログラミングにおける最大の問題です。プログラミング言語は複雑さに対処可能であるべきです。関数型プログラミングがオブジェクト指向よりも優れている点はいくつかありますが、その根幹は関数を組み合わせ可能な部品として扱う点です。各々の部分を小さな関数を組み合わせた関数として扱うことで、それぞれの抽象化のレベルにおいて理解可能な大きさで、各関数を把握できるのです。
オブジェクト指向もクラスによる再利用を目論んでいたのでしょう。ですが、継承のヒエラルキーは不必要な複雑さでコードの見通しを悪化させ、インスタンスのthis
参照super
参照はいらぬ混乱を招きます。クラスは本質的に肥大化しやすい作りになっています。機能を詰め込みすぎた所謂『神々のクラス』が問題になるのは、プログラマーの能力というよりはオブジェクト指向の思想自体が原因なのです。
もし機能の多さがプログラミング言語の優劣だとしたら、C++が最も優れた言語ということになります(多分)。しかしポインターで躓く以前に、その機能の多さがソースコードを理解不能なものにしてしまうのです(だから良いC++プログラマーは使うべき機能と使うべきでない機能をよく把握しています)。
強力な一つの機能よりも、単純で組み合わせ可能な小さな部品をプログラミングの単位とするべきです。ティム・バーナズ=リーが言うように、最も力の少ないものこそ、最も適したソリューションとなるのです。
この記事で言いたかったこと
- オブジェクト指向は難しい。
- 複雑さはそれ自体害悪である。単純なのはよいことだ。
- 「なんか新しくてすごいテクノロジー2.0」を知らないからといって、劣等感を感じる必要はない。シンプルで簡単で使いやすいツールを選べ。
- プログラミング業界の「常識」「あたりまえ」「みんな使ってるよ」を鵜呑みにするな。自分で考え、最適な技術を選べ。
- 誰かを馬鹿にするよりも、他山の石として研鑽に励もう。