Rubyの生産性の高さはどこまで本当か?


もの人がブックマークしているこの「Rubyを仕事に使うべし!」という記事で書かれているRubyの優れた特徴は、実際のところ、どの部分が、どこまで本当なのでしょうか? 少し検証してみたいと思います。

「Rubyがスゴイ」とされる点のどれがホントでどれがウソ?

「Rubyを仕事に使うべし」では、まず、Rubyの特徴として、

(1)いろんな言語のいいとこ取り
(2)構文が強力
(3)楽しくプログラミング
(4)問題が起こりにくいように設計されている

という点を冒頭で掲げています。
まず、これらをどのように検証すればよいか、考えてみます。


まず、(1)のいいとこ取りについては、いいとこ取りをし続けながらいつも進化しているのは、最近の言語はみんなそうで、それはRuby独自の特徴でもなんでもありません。
たとえば、C#は、その典型で、VM、自動メモリ管理、パッケージによる名前空間管理、マルチスレッド、ベリファイアはJavaから、GenericsはC++から、強力な正規表現はPerlから、アノニマスメソッドはLISPのラムダやクロージャーのパクりでしょう。
そして、そのパクられ元であるJava、C++、Perlもオリジナルじゃなく、さらに別言語からパクりまくってきています。*1


なので、「いいとこ取り」などという抽象的なことを言っていてもダメで、具体的に、どのような機能(の組み合わせ)が生み出すどのような具体的メリットが他の言語に無くって、Rubyにあるのか、そういう具体的な部分がキモなわけです。


そして、これは(2)〜(4)についても同じで、他の言語だって、強力な構文はあるし、やり方しだいで楽しくプログラミング出来るし、問題が起こりにくいように設計もされているわけで、すべて、個々の具体的な機能が生み出す、具体的なメリットについて、Ruby独自のものがあるかどうか、という裏付けのあるものだけが、唯一意味のあるRubyの特徴と言えるのではないでしょうか?


そして、もと記事の作者の方も当然そこのところが分かっているので、

(A)メタプログラミング
(B)ブロック構文

の2つをRubyの力の源泉としてあげています。


しかし、多くのケースで、Rubyの「メタプログラミング」と「ブロック構文」と同様のことを実現できる機能は、他の言語にもあります。それらと具体的にどのようにメリットの違いがあるのかを、具体的に比較しなければ、きちんとした比較になりません。

「ブロック構文があるからRubyが生産性が高い」というのはどこまで本当か?

たとえば、ブロック構文については、C#で、ある特定ブロックだけ別スレッドで実行したかったら、以下のように記述できます。

string cmd = "LiveForever";

if (cmd != null) {
	f.th(delegate {
		// ここからブロック開始
		f.sleep(300);
		processCommand(cmd);
		// ここがブロックの終わり
	});
}

このコードで面白いのは、別スレッドで実行されるブロックの中から、元のスレッドで使われていたcmdという一時変数に、そのままアクセスできていることです。いわゆるクロージャもどきですね。LISPプログラマからは、「20年前から知っとるわ」とか言われるでしょうけど。
ちなみに、この、f.thというメソッドは、.NETのクラスライブラリにデフォルトで組み込まれているものに、シンプルな汎用ラッパをかぶせたものです。ちなみに、その汎用ラッパは、一行で実装できます(笑)。


もちろん、これは、スレッドにしか使えない構文でもなんでもなくって、普通に、コードブロックを引数みたいに他のメソッドに渡すことができます。
たとえば、「取得する」というメソッドに、種別という名前の変数と、任意のコードブロックを引数として渡して実行するには、以下のように記述します。

取得する(種別,
	delegate{
		//ここからブロック開始
		string id = getID(tr_text, 重複);
		if (rssid != null){
			MM cell_m = y.hm(tr_text, @"(]*>(?.+?)\s*){11}");
			if (cell_m.成功) {
				try{
					MyItem item = new MyItem();

					item.ID = id;

					CaptureCollection cells = cell_m.m.Groups["val"].Captures;
					string str;
					
					str = cells[5].ToString();
					item.CurrentVal = int.Parse(str, NumberStyles.Any);
				} catch (Exception ex) {
					print(ex);
					print("tr_text:"+tr_text);
				}
			}
		}
		// ここでブロック終了
	});


ただ、これは、ダイナミック言語のように、実際にコードブロックを引数で渡しているわけじゃないです。
見かけ上、コードブロックを引数渡ししているかのように、ソースコードを記述できるだけなんですね。
実際には、C#のコンパイラが、コードブロックをメソッドに変換し、メソッドへのポインタをデリゲートオブジェクトでラップして渡しているんですね。
つまり、実際にコードブロックという、メタオブジェクトをパラメータとして渡しているわけじゃないんです。


しかし、ここで考えなきゃならないことは、プログラマーの「生産性」と「気持ちよさ」です。
メソッドにコードブロックを引数として渡しているように記述した同じソースコードが、コンパイル時にメソッドに変換されようが、実行時に実際にコードブロックというメタオブジェクトとして渡されようが、プログラマにとっては、どちらでも良いのではないのか?という点が重要なのではないのでしょうか?
ソースコードの生産性と保守性と気持ちよさが変わらないのだったら、それが内部的にどのように実行されるかなど、どうでもいいことではないでしょうか?


むしろ、C#のように、型付けの強い言語の場合、コンパイル時に多くの部分を解決することを前提とした方が、コンパイラがいろいろ潜在的に問題のあるコードを自動チェックしてくれたり、また、開発環境が型を解析して、インテリセンスやリファクタ機能などの、コーディング生産性を向上させる機能を提供してくれるというメリットもあります。*2


結局、「具体的なメリット」と、「そのメリットが具体的にどの程度の頻度で生じるか」、ということを吟味すると、少なくとも、「ブロック構文があるからRubyが生産性が高い」かどうかは、この「Rubyを仕事に使うべし!」という記事では、まるで明らかにされていないのです。


そもそもメタプログラミングとは何か?

メタプログラミングとは、要は、プログラム自体を操作するプログラミングです。
たとえば、クラス、メソッド、コードブロックといったプログラムの構成要素を操作するわけです。
単純なものだと、あるクラスのクラス名を文字列として取得するプログラムも、ごく原始的なメタプログラミングと言えます。


そして、メタプログラミング機能は、おおざっぱに、読み出し系と書き込み系に分けられます。


たとえば、読み出し系の例では、実行時に外部から読み込んだXMLファイルの中に記述されたクラス名のクラスのオブジェクトを生成し、通信でサーバから送られてきたメソッド名のメソッドを実行するといったケースが、これに当たります。

また、たとえば、RDBのテーブル名と同じ名前のクラスを取ってきて、そのクラスのインスタンスをそのテーブルに格納されているレコードの数だけ生成し、そのレコードのフィールド名と同じ名前のインスタンスフィールドに、データを格納する、というプログラムが書けます。


もし、この読み出し系メタプログラミング機能がなかったら、RDBのテーブルごとに、このデータを読み出してオブジェクトにするルーチンを別々に用意しなければならないのですが、読み出し系メタプログラミング機能のおかげで、1回書けば、それを使い回せます。


ここでのミソは、プログラミング時には、それがどんなクラスで、どんなメソッドを持つか分からないのに、とにかくそのインスタンスを生成して、そのメソッドを実行する、というプログラムが書けてしまうことです。ソースコード内に、字面として現れないクラスやメソッドを実行できることがミソなわけです。



一方で、書き込み系のメタプログラミングは、たとえば、実行時に新しいクラスやメソッド自体を合成したり、書き換えたりする操作です。


これを使えば、たとえば、複数のクラスを実行時に足し合わせるようなことが出来ます。なので、多重継承を許さない言語処理系で、多重継承もどきのことができたりします。


あるいは、継承のセマンティックスを拡張して、フィールドの初期値を集合演算のunionを取るようにすることもできます。たとえば、クラスAの「使える色」という定数フィールドの値が、「赤,青」だったとして、そのクラスを継承するクラスBで、「使える色」というフィールドの値を「青,緑」にしておくと、継承時に集合演算されて「赤,青,緑」というフィールド値になります。


メソッドの例では、たとえば、アスペクト志向プログラミングのように、既存のコードをいじらずに、実行時に、ある特徴をもったメソッドが実行される前に、実行され、それらのメソッドの動作ログをとるといったような使い方が出来ます。


これらのメタプログラミング機能を、どこまでサポートしているかは、言語によって、それぞれ異なりますが、少なくとも、読み出し系については、C#やJavaにかぎらず、VisualBasicなど、最近のメジャーな言語の多くではサポートされています。


「メタプログラミングが強力だからRubyが生産性が高い」というのはどこまで本当か?


キモは、これらのメタプログラミング機能の具体的メリットと、そのメリットが生じる頻度です。
そして、それは、読み出し系と書き込み系に分けて考えるべきでしょう。

まず、読み出し系についてですが、確かに、読み出し系のメタプログラミングは、具体的な生産性や気持ちよさにはかなり貢献するのですけど、読み出し系メタプログラミング機能は、JavaやC#やVisualBasicなど他の言語にもあります。


なので、Rubyの読み出し系メタプログラミング機能と他の言語の読み出し系メタプログラミング機能で、具体的にどのような効用の差があるのか、ということです。

ちょっと冷静に考えてみれば分かると思いますが、読み出し系メタプログラミングで扱うオブジェクトなんて、クラス、メソッド、フィールド、プロパティなど、どの言語にもありふれた、退屈なほど分かりやすいオブジェクトでしかありません。クラスオブジェクトからメソッドオブジェクトを取り出すAPIは、ようは、単にメソッド名やメソッド種別を引数として指定して取り出すだけで、どう見ても、言語に関係なく、複雑な操作にはなり得ません。


たとえば、C#で、ある特定のオブジェクトのクラスの、フィールドオブジェクトの配列を、インスタンスフィールド限定で、取り出すには、次の一行で済みます。

FieldInfo[] fa = o.GetType().GetFields(BindingFlags.Instance);

メソッドも同様です。

MethodInfo[] fa = o.GetType().GetMedhods(BindingFlags.Instance);

また、取り出したメソッドオブジェクトの中から、ある特定の名前のメソッドを探したければ、

foreach (MethodInfo m in ma) if (m.Name == name) return m;

で事足ります。
もちろん、わざわざループを回さなくても、メソッド名と種別を指定して、一発でメソッドオブジェクトを取り出すAPIも処理系が用意しています。

また、取り出したメソッドオブジェクトに引数を渡して実行するには、

object 引数リスト = new object{7,"実行したよん。"};
meth.Invoke(o,引数リスト);

とするだけです。
どの操作も、なんも、ややこしいことはありません。


この程度のことをするのに、Rubyと他の言語で生産性にそれほど大きな差が出ることは、とても考えにくいです。


なので、元記事の「Rubyを仕事に使うべし!」では、

 Javaでのメタプログラミングは,リフレクションAPIやバイトコード操作*3を用い,ときにはJava仮想マシン(Java VM)の仕様の知識まで要求される難易度の高いものです。

と書いてありますけど、これは、少なくとも、読み出し系には当てはまりそうにありません。




次に、書き込み系のメタプログラミング機能ですが、実は、書き込み系のメタプログラミング機能は、読み出し系メタプログラミング機能と柔軟なクラスライブラリの組み合わせで、置き換え可能なことがほとんどなのです。


とくに、C#のカスタム属性と読み出し系のメタプログラミング機能を組み合わせれば、たいてのものは、かなりエレガントに実装出来てしまいます。もちろん、同様の機能はJavaにもあります。


たとえば、先ほどの、フィールドの初期値を集合演算で継承するというように、継承のセマンティクスを拡張したクラスをどう実装するかというと、単に、カスタム属性(たとえば、Union継承、など)でマークされたクラスをプログラム中からリストアップして、そのクラスオブジェクトの継承関係をさかのぼっていって、やはり、あるカスタム属性でマークされたフィールドをリストアップして、その初期値を取り出して、集合演算して、格納し直す、という処理をすればいいだけです。


また、どうしても、実行時に、二つ以上のクラスを合成して、多重継承のようなことをさせたいという場合、単に、ハッシュテーブルにメソッドを格納して、オブジェクトの代用として使えば、実用上は、それで事足りちゃったりするんですよね。
つまり、複数のクラスから、それぞれ、メソッド一覧を取り出し、そのメソッド一覧と、その所属インスタンスをペアにして、メソッド名をキーにして、ハッシュテーブルに格納しておくとか。

その、多重継承オブジェクトのメソッドを呼び出すとき、いちいち文字列で記述するのがウザイし、タイプミスが起きるのがイヤだというのなら、C#のenumを使えばいい。場合によっては、特定の処理だけ、外部のパラメータファイルで記述してもいいし。

もちろん、書き込み系のメタプログラミング機能を使った方が、これらは、エレガントにかけますよ。でも、圧倒的多数のプログラマが日々格闘している、圧倒的多数のアプリケーションでは、生産性も気持ちよさも、実際には、それほどかわらんですよ。


もちろん、まったく新しいプログラミングパラダイムの要求されるシステムとか、そういう研究室レベルのプログラミングは別ですよ。書き込み系のメタプログラミングができるとできないとで、大きな差がでるケースもあるかもしれません。でも、そういうプログラムは、そもそもRubyなんかじゃなく、Rubyなんかよりも、もっとずっと本格的なメタプログラミングのできるCLOSとかで書くべきでしょう。


こうしてみると、読み出し系にしろ、書き込み系にしろ、「メタプログラミングが強力だからRubyが生産性が高い」というのは、少なくとも、あの記事からは、読み取れないということが分かります。


その他、細々とした部分

まず、Javaのループをわざわざカウンタを回すなど、冗長な書き方にして、シンプルなRubyのループ記述と比較していますが、これはフェアではありません。実際には、Javaでは、以下のようにカウンタを使わない、シンプルなループ記述ができます。

for (int i : integers) {
    System.out.println(i);
}

これだと、Rubyのシンプルなループ記述とそれほど大きな差はありません。
もちろん、C#でも同様に、シンプルなループ記述ができます。

また、Rubyのシンプルなプロパティ定義として、以下のようなサンプルがあり、setterとgetterをわざわざ記述しなければならないJavaの冗長さと比較していますが、これもフェアではありません。

class Foo            # クラスFooを定義


  attr_accessor :bar # プロパティbarを定義
  attr_reader :baz   # プロパティbazを定義
end


foo = Foo.new # Fooオブジェクトを作成
foo.bar = 23  # プロパティbarに値を設定
puts foo.baz  # プロパティbazから値を取得して出力
foo.baz = 42  # エラー。bazにはsetterが定義され


そもそも、フィールドでなく、わざわざプロパティにするのは、読み出しと書き込みの際に、なんらかの処理や制限を加えたいからです。
単純に読み出しと書き込みの両方を許可するだけのプロパティならば、それは、単なるフィールドとなんら変わりませんので、読み出しと書き込みの両方が出来るだけのプロパティがシンプルに記述できることなど、メリットではありません。
そして、もし、読み出しと書き込みの際に、プログラムコードによって処理や制限を加えたいのであれば、RubyだろうとJavaだろうとC#だろうと、言語に関係なく、setterとgetterに相当するメソッドは記述しなければなりません。
もちろん、setter/getterの記述が煩雑かどうか、という話ですが、たとえば、C#のsetterとgetterの記述は、かなりシンプルになっています。わざわざそれが煩雑なJavaと比較して、Rubyの記述がシンプルであるとしても、それは、Rubyがシンプルなのではなく、RubyやC#に比べると、Javaの記述がやや煩雑だというだけの話です。


もちろん、わざわざsetterやgetterを記述するまでもなく、単に、読み出しか書き込みのどちらか片方だけを許可したい場合もあります。
しかし、たとえば、上記のattr_readerのケースのように、単に読み出しだけで、書き込みを禁止するプロパティーのシンプルな記述をしたい場合、
まず第一に、フィールドを指定して、それをラップするプロパティーを自動生成し、かつ、そのフィールドへの全てのアクセスをプロパティへのアクセスに変換してくれるリファクタツールのある開発環境を使っているのは、すくなくとも現在ではありふれた話ですので、そういうケースでは、単に自動生成し、setterの部分のコードを削除すればいいだけなので、他の言語と、生産性にたいした差が出ません。たとえば、C#の場合、VisualStudioにそういう機能があります。
第二に、たとえ、開発環境の機能を使わず、完全に手だけでハンドコーディングしたとしても、それほど大した手間じゃありません。
たとえば、C#だと、上記のbazプロパティは、次のようにかけます。

int _baz;
int baz{ get{ return _baz;}}

この程度のわずかなシンタックスシュガーで、それほど生産性に大きな違いがでるのでしょうか?


最後に、問題が出ないように設計云々ですが、たとえば、リソースの解放し忘れでトラブルが起きないようにする構文は、たとえばC#でもusingとして使えます。もっと言うと、Rubyのような動的言語より、JavaやC#の方が、実行時に問題を引き起こすコードが書けないように、制限する機能については、ずっと充実しているという見方すらできます。特に、難易度の高い部分と低い部分を切り分け、経験の浅いプログラマが、おかしなコードを記述してシステムトラブルを防ぐようなフレームワークを記述する能力についてJavaやC#がRubyよりも劣っているという議論は、かなりムリがあるのではないでしょうか。


結論

というわけで、少なくとも、「Rubyを仕事に使うべし!」ではRubyの生産性が飛躍的に高いということの、説得力のある説明にはなっていないことがわかります。
そして、これは、他のRubyの解説記事を分析しても、同様で、Rubyの生産性の高さを喧伝する記事は、冷静に吟味してみると、どうも説得力に欠けるものばかりです。
少なくとも、私は説得力のあるRubyの生産性についての記事にお目にかかったことがありません。
見つけた方は、ブクマコメントか、トラバでご一報いただけると幸いです。



追記:理想と現実

トラックバックで、「どの言語でも生産性にたいして違いがないことなど、当たり前のことで、オマエがごちゃごちゃ解説するまでもなく、もとから知っとるわい」という趣旨のツッコミが、複数入りましたので、ちょっと補足を入れます。


まあ、確かに、上記の文章は、「圧倒的多数のケースでは、生産性の違いは、プログラミング言語自体から来るものではない」という、「そんなもんわざわざオマエに言われんでも知っとるわい」と言われそうな、当たり前のことをごちゃごちゃ論証してるだけではあります。


トータルの生産性は、使えるライブラリだとか、実行環境のサポートだとか、レガシーコードとの相性だとか、開発環境との相性だとか、開発メンバのスキルとの相性だとか、そいういう雑多で退屈な文脈的ファクターで決まってくるもので、「文脈から独立した、プログラミング言語自体の生産性」というものを議論すること自体が、多くの場合、机上の空論でしかなくなってしまう、という、とても常識的で、当たり前で、退屈で無難な結論です。つまらん大人が言いそうなサゲサゲな結論です。


要するに、ぶっちゃけて言うと、Railsがスゴイのであって、Rubyがスゴイわけじゃない、ということです。


が、もちろん、もっと子供的な、少年が目をキラキラさせて語るような、「自分の創造性と生産性をフルに引き出してくれる、究極に気持ちよい言語はどれなのか?」ということに、プログラマーは興味を持つものなんですよね。


でも、そういう雑多なしがらみや文脈を一切無視して、純真無垢な子供のように、純粋に自由度と生産性と気持ちよさを追求して良いのなら(もちろん、そんな前提条件は、現実にはまず成立しないですが(笑))、たとえば、

Lisp-CLOS >>> Ruby

という結論になる人もそれなりにいるのではないかとも思います。少なくとも、Lisp-CLOSを熟知している人ならば。あと、括弧にアレルギーがなければ(笑)。


とくに、LispのdefmacroとCLOSのコンビネーションはすばらしく「気持ちいいプログラミング」タッグで、これに慣れちゃうと、現在広く使われているメジャーな言語処理系は、ほとんど全てムカムカするなんて人もいるんじゃないでしょうか。いや、これは、別に、Lispがすごいわけじゃなく、defmacroがすごいだけで、defmacro的機能があれば、別にどんな言語でもかまわないのか。むしろ、括弧だらけのLisp風S-Expressionなんか使わず、ある特定のやり方で宣言したブロック内では、全く自由なシンタックスとセマンティックスでプログラムを記述できて、それが、S-Expressionに変換されて、引数として受け取り、自由にRubyのメタオブジェクトに変換できるようなdefmacro機能がRubyにあるとうれしいんだけど。
というか、Rubyにもdefmacroみたいな機能はもうすでに入ってたりしないのかな?Lispのdefmacroほどでないにしろ。RubyではDSLが定義できるらしいから、それに近いことはできるはずだと思うんだけど。


ちなみに、defmacroは、引数としてスタティックな「コードブロック」を受け取るのではなく、引数として、「コードのシンタックスツリーの生構造」を受け取って、その生構造をプログラム的に変換した生構造を返値として返し、それが実行されるようにできる仕組みなんですね。


これは、「データとプログラムコードに区別がなく、データもプログラムも、すべてS-Expressionという形式で表される」というLispの強みをフルに活用した機能で、これがもたらす自由度はとても大きく、極端な話、Haskell的機能を使えるようにするライブラリですら作れます。


というか、これは、defmacroのパワーというより、S-Expressionのパワーか。defmacroを使わなくても、単に、アトムにS-Expressionをデータとしてバインドしておくだけで、それを実行時にプログラムに変換しながら実行するなんてことも出来ますから。


で、その自由度が、超強力なメタオブジェクトシステムであるCLOSと結びつくと、さらに面白いことになるわけです。


もちろん、このdefmacro機能は、JavaやC#のようなコンパイラ型言語にも、理論上は追加可能じゃないかとボクは思っていて、要は、コンパイル時にソースコード内の指定ブロックを指定の規則(BNFだの正規表現だので指定した規則)に従って粗くparseしてS-Expressionもどきのプログラマブルな形式に変換し、引数として渡されて、プログラムのASTに変換するような、プリプロセッサとして動作するメソッドを定義できるようにすればイイだけなのじゃないかと思います。コンパイル時にだけ実行されるメソッドですね。


そして、ぼくは、Lispは、JavaやC#や多くのLL言語の過去であると同時に、未来でもあるんじゃないかと思ってます。



プログラミング言語の進化の歴史とは、抽象化の歴史でもあります。
そして、抽象化の目的は、2つです。
一つは、処理系による自動最適化です。プログラムコードは、抽象度が高いほど、自動的に最適化し、パフォーマンスを出しやすいのです。
今後も、処理系がインテリジェントになって最適化能力が高まれば高まるほど、プログラミング言語は、最適化のための抽象化を求められ、高級化していくと思います。
そして、もう一つは、自由度と生産性です。より、思ったことを、縦横無尽に書けるようにするため、プログラミング言語は、抽象度が高くなっていきます。
そして、後者の自由度と生産性を目的とした抽象化の行き着く先には、本質的には、Lisp-CLOSにおける、S-Expressionやメタオブジェクトプロトコルと同様のものになってしまうのではないかという予感があるのです。


もちろん、これは、今現在の話ではなく、今現在の話として考えると、「ライブラリや開発環境や普及率や素人受けしやすさやシステムの値段や重たさやレガシーなどの、文脈を無視すれば」という、でっかいIFがついたとしての話になってしまうので、現実には、Lisp-CLOSみたいな重たくて、ライブラリが弱くって、括弧アレルギーを引き起こす、普及していない言語なんかでシステム開発をするのは、まず、ほとんどあり得ないですけど。


ちなみに、Lisp-CLOSは単なる研究室の中にしかない言語ではなく、商用のWebアプリケーションがCLOSで実装され、運用されている例もあるようです。

*1:たとえば、JavaはC++からもたくさんパクりまくってるけど、C++にない、 自動メモリ管理(GCね)、マルチスレッド、パッケージによる名前空間管理などは、Javaが出るはるか前から、CLOS(Common LISP Object System)を実装したアレグロCommon Lispではフルに使えてましたし、露骨にそこらへんからのパクりでしょう。というか、そもそも、Common Lisp仕様策定メンバが、Javaの仕様策定メンバに入ってなかったっけ? Guy L Steel Jr.とか。Javaが最初に発表されたとき、すでに、CLOSもC++も長年ヘビーに使い込んだことのあったオイラは、その機能群に「うひょー。なつかしー。」と思わずつぶやいちゃったほど、Javaは「いいとこ取り」だらけの言語なわけです。

*2:話は脱線しますけど、型については、もちろん、アプリの種類によっては、型付けがすごい邪魔で面倒になったりもするのですが、逆にすごいメリットになることもあるので、型があること自体は、単に向き不向きの問題に過ぎず、それ自体は善でも悪でもありません。ただし、型があると、気持ちよさがだいぶダメージを受けます。型があると、コンパイラが小学校の先生とか母親みたいに、いちいち小うるさいことを言うので(ちょっと散らかしただけなのに、いちいちすぐ片付けろとか)、気分を害することが多いのです。ただし、そんなお小言によって、危険なバグが未然に防がれることも多く、それを実感すると、お小言も、まあ、いいかな、とか思ったりもするのですが

'; break; case 'horizontal': if(a.imgUrl){ imgCode = ''; } code = '
'+ '
'; code += imgCode; if(a.label) code += ''+a.label+''; code += '
'; break; default: error('こんなview知らねえ。'+a.view); } return code; } function toImgUrl(super_kind){ var HFotoTbl = { tw: '20180803102713', fb: '20180803102707', hb: '20180803102710', line: '20180803102711', pocket: '20180803102712', feedly: '20180803102708', hate_blo: '20180803102709', note: '20180821103319' }; var hf_id = HFotoTbl[super_kind]; if(!hf_id){ log('このsuper kind:'+super_kind+'のはてなフォトIDが登録されてないぞ。'); return null; } return hfid2url(hf_id); } var HFDayRE = /^\d{8}/; function hfid2url(hf_id){ var sa = HFDayRE.exec(hf_id); return 'https://cdn-ak.f.st-hatena.com/images/fotolife/f/fromdusktildawn/'+ sa[0] + '/' + hf_id + '.png'; } function setButton(a){ var pos = isString( a.pos ) ? $(a.pos) : a.pos; a.imgUrl = toImgUrl(a.superKind); var html = makeButtonHtml(a); var meth = a.insertMethod; if(!meth) meth = 'after'; pos[meth](html); $('#'+a.id).on('click',function(){ window.location.href = a.url; }); } function asurePrefix(a){ if(!a.prefix){ var random = Math.floor( Math.random() * 110000000000000 ); a.prefix = 'random-'+random+'-'; } } function setSocialBts(a, defs){ var title = getPageTitle(a.title); var url = getPageUrl(); if(!a.insertMethod) a.insertMethod = 'after'; var b = $.extend(true, {}, a); asurePrefix(b); var i=0; b.title = encodeURIComponent(title); b.url = encodeURIComponent(url); var pos = b.pos; var id; defs.forEach(function(def){ var c = $.extend(true, {}, b); var kind = def[0]; c.label = def[1]; id = c.id = c.prefix + kind; c.pos = pos; c.kind = kind; var superKind = SuperKindTbl[kind]; c.superKind = superKind ? superKind : kind; var site = SITE_NAME; if(site == 'fromd.hateblo.jp') site = 'www.furomuda.com'; switch(kind){ case 'tw': c.url = 'http://twitter.com/share?url=' + c.url + '&text='+c.title+encodeURIComponent(' @fromdusktildawn ')+ '&related=fromdusktildawn'; break; case 'fb': c.url = 'https://www.facebook.com/sharer/sharer.php?u='+c.url; break; case 'hb': c.url = 'http://b.hatena.ne.jp/add?mode=confirm&url=' + c.url + '&title='+c.title; break; case 'line': c.url = 'https://social-plugins.line.me/lineit/share?url='+c.url; break; case 'pocket': c.url = 'http://getpocket.com/edit?url='+c.url+ '&title='+c.title; break; case 'twFollow': c.url = "https://twitter.com/intent/follow?screen_name=fromdusktildawn"; break; case 'hbFollow': c.url = "http://b.hatena.ne.jp/fromdusktildawn/"; break; case 'fbPage': c.url = 'https://www.facebook.com/furomuda/'; break; case 'fbGroup': c.url = "https://www.facebook.com/groups/426678561170457/"; break; case 'hate_blo': c.url = "https://blog.hatena.ne.jp/fromdusktildawn/"+ site + "/subscribe"; break; case 'note': c.url = 'https://note.mu/fromdusktildawn'; break; case 'feedly': c.url = 'https://feedly.com/i/subscription/feed/'+ encodeURIComponent('https://'+site+'/feed'); break; default: console.log('こんなkindしらねえ。:'+kind); } setButton(c); if(c.insertMethod == 'after'){ pos = '#'+id; } }); return id; } function make2ColButtons(a, ldef, rdef){ var col_ids = make2ColFrame(a); var lpos = '#'+col_ids.l; var rpos = '#'+col_ids.r; a.pos = lpos; setSocialBts(a, ldef); a.pos = rpos; setSocialBts(a, rdef); return col_ids.frame_id; } function make2ColFrame(a){ asurePrefix(a); var LEFT_SUFFIX='-left-cell'; var RIGHT_SUFFIX='-right-cell'; var prefix = a.prefix; var frame_id = prefix+'-frame'; var left_id = prefix+LEFT_SUFFIX; var right_id = prefix+RIGHT_SUFFIX; var frame = '
'+ '
'+ '
'+ '
'; $(a.pos).after(frame); return {l: left_id, r: right_id, frame: frame_id}; } // PC依存のコード========================================== var archiveEntries = $(".archive-entry"); if(archiveEntries.length){ archiveEntries.each(function(){ var archiveEntry = $(this) var entryTitleLink = archiveEntry.find(".entry-title-link"); var entryHref = entryTitleLink.attr("href") var entryDescription = archiveEntry.find(".entry-description"); var bookmarkHtml = " " entryTitleLink.append(bookmarkHtml); var readMoreHtml = "もっと読む"; entryDescription.append(readMoreHtml); var bookmarkCounter = archiveEntry.find(".bookmark-widget-counter"); bookmarkCounter.hide(); var starContainer = archiveEntry.find(".star-container"); var bigBookmarkCounter = archiveEntry.find(".big-bookmark-counter"); starContainer.insertAfter(bigBookmarkCounter); function makeStarCountBig(){ var starCount = archiveEntry.find(".hatena-star-inner-count"); if(starCount.length){ starCount.css('font-size','140%'); } else { setTimeout(makeStarCountBig,300); } } makeStarCountBig(); }); // フォローしてもらうボタンをサイドバーに設置 var btPos = '.profile-description'; var startPos = 'my-buttons-pos'; $(btPos).after( '
' ); twPos = '#'+startPos; var last_id = setSocialBts({pos: twPos}, [ ['twFollow', 'フォローする'], ['note', 'フォローする'], ['hate_blo', '読者になる'], ['feedly', '購読する'], ['hbFollow', 'フォローする'], ['fbPage', 'facebookページ'], ['fbGroup', 'facebookグループ'] ]); var paddingBottomId = 'my-padding-bottom-id'; $('#'+ last_id).after( '
' ); var pos = $('#main-inner'); var img_url = hfid2url('20201008224538'); //var img_url = hfid2url('20201003093324'); //var img_url = "https://storage.googleapis.com/hateblog/img/prj/book/learning/learning_blog_top.png"; pos.before( '
'+ ''+ ''+ ''+ '
' ); /* var free_a_begin = ''; var book_a_begin = ''; pos.before( ''+ ''+ ''+ ''+ ''+ ''+ ''+ '
'+ free_a_begin+ ''+ ''+ 'ふろむだ本のガッツリ5章分が無料のWeb記事として読めます!

'+ free_a_begin+ '無料版を読むにはここをクリック'+ '
'+ book_a_begin+ '本で読みたい方はここをクリック'+ '
' ); */ } else { var title = $(".entry-header .entry-title a"); if(title) { var titleHref = title.attr("href") var bookmarkHtml = "" var entryTitle =title.parent(); entryTitle.append(bookmarkHtml); entryTitle.css('font-size','150%'); function addHeadStar(){ var starCountCheck = $(".entry-footer .hatena-star-star-container .hatena-star-inner-count"); if(starCountCheck.length){ var starContainer = $(".entry-footer .hatena-star-star-container"); if(starContainer){ var starCount = starContainer.find(".hatena-star-inner-count"); starCount.css('font-size','24px'); var starClone = starContainer.clone(true,true); var bigBookmarkCounter = title.parent().find(".big-bookmark-counter"); starClone.insertAfter(bigBookmarkCounter); starCount = starClone.find(".hatena-star-inner-count"); starCount.on('click',function(){ alert('☆を付けた人の一覧を見るには、この記事の下部の☆数をクリックしてください。'); $("html,body").animate({scrollTop:$('.entry-footer .hatena-star-star-container').offset().top-100}); }); var bt = starClone.find(".hatena-star-add-button"); bt.on('click',function(){ alert('☆を付けるには、この記事の下部の「☆+」ボタンをクリックしてください。'); $("html,body").animate({scrollTop:$('.entry-footer .hatena-star-star-container').offset().top-100}); }); } else{ setTimeout(addHeadStar,300); } } else { setTimeout(addHeadStar,300); } } addHeadStar(); procSpecialMarkers(); var param = { pos: 'footer.entry-footer p.entry-footer-section', title: title.text() } make2ColButtons(param, [ ['tw', 'この記事をツイートする'], ['hb', 'この記事をブックマークする'], ['pocket', 'この記事を後で読む'] ], [ ['fb', 'この記事をシェアする'], ['line', 'この記事をLINEで送る'] ] ); // フォローしてもらうボタンを下部に設置 param = { pos: 'footer.entry-footer div.social-buttons' }; var frame_id = make2ColButtons(param, [ ['twFollow', 'フォローする'], ['hate_blo', '読者になる'], ['hbFollow', 'フォローする'], ['fbGroup', 'facebookグループ'] ], [ ['note', 'フォローする'], ['feedly', '購読する'], ['fbPage', 'facebookページ'] ] ); } } // 記事一覧ページと、単体表示ページの両方に共通するコード ==================== var d = $('div.date a time'); d.css({ 'color': '#aaa', 'font-weight': 'normal' }); if(IS_ANNEX){ var link = $('h1#title a'); // ブログタイトルのリンク link.attr("href", 'https://www.furomuda.com/'); } })