負け犬プログラマーの歩み

負け組だった人間が今一度PGとして人生の飛躍を模索するも加齢と共に閉ざされる未来に直面しているブログ。

俺を雇う会社はないのかなあ

今年の3月で今の会社との契約が切れる。

次はUnityをやってみたいが、もちろん未経験。それどころかB2C自体ほとんど知らない。いくらC#ができても多分厳しいだろうね。

Webはフロントもサーバーも大体できるようになって、あとはとりかかる案件ごとに採用されている特定のフレームワークの知識をどこまで高めるかって状態になってしまったから少し面白みがない。ただし、Railsフレームワークの中身にもそれなりに詳しくなってきたし、今後はDjango、Express/Sails、ASP.NET MVC、PLAY!とかだったらやってみたいなあ。BackboneやAngularJSも機会があれば一度担当できると助かる。

英語は会話は無理だが読み書きは全然問題ないので外資系企業で働けたら少しうれしいのだが。この「人よりは英語ができる」という自分の付加価値を評価してくれる会社がどこかにあればなあ。

でも一番は俺のことを技術者として見てくれる会社に行きたいなあ。

C#
VB
Ruby
JavaScript
HTML/CSS
Oracle
MySQL
SQL Server

この1年でHTML/CSS系は本当に伸びたと思う。レスポンシブ対応やIE678対応とかも割と多く担当できたし。

キャプチャしない正規表現は()にする・・・って、え?

俺は正規表現が得意ではない。しかし、凡そどんなプログラミング言語でも役に立つ技術なので覚えておいて損はない。だから最近になって頑張って勉強をして、業務でも適切な機会があれば必ず正規表現を用いて書くようにしている。言うまでもなく慣れてくると非常に面白い。正規表現リテラルと称して/^hoge$/と簡単に書けるRubyJavaScriptの秀逸な設計を改めて認識してしまう。

話は変わってこんな記事がある。いい話(ウェブリオ辞めました) - アスペ日記

はてぶ数が非常に多く、退職エントリまとめでも最上位(ちなみに1位はこの人がGoogleを辞めたとき)。京大大学院卒で元Google社員という経歴から見れば、こんな浅学菲才の俺のような人間とは少し世界が異なるとは言わざるを得ない。プログラマーとしてのキャリアも翻訳ソフトで5年間、Googleで1年と俺のキャリアよりも全然長い。ただ、今日改めて読み直してみて話の核心部分に「んん?」と思うような部分があったので、少し感じたことを書いてみる。

まずは、引用。

ぼくは、キャプチャしない正規表現を (?:) でなくただの () で書くようレビューで高圧的に言われたとき、今の職場を辞めることを決意した。(まだ辞められていないのは純粋にぼくが無能なせいだ)

キャプチャしない正規表現を () で書くこと自体は、そこまでの悪じゃない。
ぼくも昔はそう書いていたし、(?:) を知ってからも使い続けていたし、今でもワンライナーならそう書くかもしれない。

しかし、そう書くことを強制されるというのは、まったくの別問題だ。
それは人間としての尊厳に関わることだ。
「() で書いたほうがシンプルで読みやすい」と言われたら、確かに理がないわけではない。
でも、そのトレードオフで、そのいわゆる「読みやすさ」を重視することを選ぶ根拠、それを押し付けられる根拠は何だ? ということを突き詰めたら、相手のほうが「偉い」という「権力」しかない。

要は、

①「キャプチャしない正規表現()と書け」と言われた
()と書くことは間違ってないし自身は昔も今もそう書くことがある
③ただし強制されたのが気に食わない

という思考の流れなのであるが、これが理解できない。特に赤字の部分がおかしい。

つまり、このブログ主は「()はキャプチャしない」ことは自分の経験から正しいと認めた上で、相手の主張は個人的には呑めないと言っている訳だ。恐らくキャリアからして正規表現には相当慣れ親しんでいる方と推測されるが、しかし今日の一般的な正規表現のルールから見ればこれは明らかに間違いだ。というのも()でグループ化された部分は後から参照するかはともかく)キャプチャされる訳で、むしろそれを回避するために(?:)が有るのだ。

しかし、問題はコメント欄でもそれを指摘する意見はひとつもない。はてなでも一件だけ「意味が分からない」というコメントがある以外はスルー。Twitterでもそれらしい指摘もない。ということは、違和感を持つ俺が少数派なんだと思う。

そもそも、ただのブログエントリーだ。それも愚痴混じりの。だから推敲せずに書いていたりもあるだろうし、ご本人の意図よりも更に大きく拡散されたという事情もあるだろう。純粋な日本人であっても肯定と否定を書き間違えて投稿してしまうことも普通にありえるし、大抵の人は「キャプチャしない正規表現でも()と書けと言われた」という風に補完解釈したのだろう。しかし、それにしても誤解を招きやすい表現だ。言葉足らずにも程があるし、俺みたいに正規表現に弱い人間は暫く意味が分からず戸惑ってしまう。

最大限に推測を加えた結果、最終的な話の帰着点はこうなるのではないか。

後方参照しない(または名前つき後方参照のみを用いる)部分を(?:)で明示的にキャプチャから外していたが、上司からは「読み易さのために()でキャプチャしたままにしておけ」と言われた。


なお、正規表現の聖書とも言われることがあるオライリー「詳説 正規表現」にこんな一説がある。



 括弧の中の部分式にマッチしたテキストはキャプチャされ、$2に格納される。そして、ここではこの変数を使わない。グループ化には使えるが、使うつもりのない変数へのテキストの格納というオーバーヘッドのない(そして混乱を起こす危険性もない)括弧のタイプがあれば、もっとよくなるのではないのだろうか。
 Perlなどの新しい正規表現は、そのための方法を提供している。グループ化してキャプチャする「()」ではなく、「(?:)」という特殊な記法を使えば、グループ化はするがキャプチャはしない
 この方法によって得られるものは2つある。1つは、不要なキャプチャを避けることによってマッチ処理の効率が上がることだ。もう1つは、それぞれの目的のために必要なタイプの括弧を使えば、後でコードを読む人を混乱させずに、コードの意味をはっきり伝えられるということだ。目的にかかわらず同じ括弧が使われていたら、どういう意味で括弧が使われているのか迷うことになるだろう。
 しかし、「(?:)」という記法はちょっと不恰好だし、正規表現を一目で把握するということではかえってマイナスになっているのではないかとも考えられる。得られるメリットは。このコストに見合うものだろうか。私自身は、必要なタイプの括弧を正確に使うのを好む方だが、この例に限っては、わざわざ混乱を招く必要はないように感じる。たとえば、マッチは1回だけなので(ループで繰り返し行われているわけではないので)、効率が本当に問題になるようなケースではない。
 この章では、キャプチャが不要な場合でも、見た目がすっきりしている「()」をつかっていくことにする。

JavaとC#のパフォーマンスを比較する。

実装やコンパイラの性能次第で幾らでも変わると思うのだが、プログラミング言語の速度については、大体以下のようにグループ分けして考えている。

f:id:Cedille:20150117230403p:plain

同グループ内であれば速度は似たり寄ったりなので「どっちが速いのか」という思索に至ることはなく、例えば「JavaC#のどちらが速いのか?」という点についてはこれまであまり意識することはなかった。

ところが、今日こんな記事を見た。

JavaとC#の「int」の比較 - Qiita

有り難いことに、昔書いたエントリーが紹介されており、面倒臭かったから探しもせず、計測もしなかったJavaのプリミティブ型とC#のデータ型のパフォーマンスを調べて頂いており、5万回ずつ単純な数値型を増減する処理を実行しており、最後にはこういう結果が出ていた。

Java : 2783ms.
C# : 15236ms.
という結果が出ました!

これは少し驚きだ。

というのもC#の方が6-7倍も遅いのだ。俺の言語速度のヒエラルキー図が完全に崩れてしまう。とりわけ上の記事で実行されていたC#コードは、構造体からオブジェクトへのボックス化が発生することはないため、C#が特段不利になるようなものでもないと解される。

だが、いざ実行されたコードを試してみると、その理由が理解できた。というのも、上記の結果はデバッグビルトのものであり、恐らくVisual Studio上で実行したものだと解される。だからC#にものすごく不利な条件で測定されたものと解される。どうして不利かというと、要はJITコンパイルで最適化されていないのである。

上記のコードをデバッグとリリースモードでC#コンパイラがILにしたものが以下である。

Debug

 ILを見る

Release

 ILを見る

わざわざ長いコードを貼って恐縮ではあるが、生成されるILにあまり大きな違いはない。}の部分でもブレークポイントを設置できるようにILでnop(No Operation)命令が埋め込まれていたりするが、これだけでは流石に大きな違いは出てこない。一番の違いは、この後更にJITコンパイラがネイティブコードに変換するときに、メソッドのインライン展開やレジスタ割り当てなどの最適化が実施される。特にこのコード例の場合、レジスタ割り当てがかなり効きそう。

ところで、比較するならJavaについても調べないといけないが、C#みたいにIDEで実行したかどうかで違いがあるのかどうか分からんが、とりあえずコマンドラインでjavacして実行してみると・・・「3ms」とか「4ms」とか異様に速いんだけどッ!? …ってコードをよく見たら、恐らくVMが最終的にansというローカル変数が使われないからって最初っから計算していない可能性が高い。という訳でコードを以下のように修正してもう一度実行したらちゃんとした値は出た。この辺りはJavaVMの方が頭が良いとは感じた。

public class TestOfInt {

    /**
     * @param args
     */
    public static void main(String[] args) {
        int ans = 0;

        long start = System.currentTimeMillis();

        for(int j = 0; j < 50000; j++) {
            for(int i = 1; i <= 50000; i++) {
                ans += i;
            }

            for(int i = 1; i <= 50000; i++) {
                ans -= i;
            }
        }

        long end = System.currentTimeMillis();

        System.out.println(end - start + "ms. " + ans); // ここを修正
    }
}


最終的な結果はこうなった。なおCore™ i7-3770KとDDR3 16GB。

C#(リリース) 1411ms
Java     1508ms
C#(デバッグ) 14051ms

なぜVBはC#と比べて駄目なのか

来年3月で1.5年ほど働いた現場を去る。非常に働きやすい職場だったし、周囲の人間もみんな良い人ばかりだった。残業をそれなりにして520万程度だが収入面も「極めて安い」という訳でもなかった(2015/6追記。やっぱ安い)。だが、ここでの経歴や実績は、「ASP.NET」とだけ書き、フロントサイド(レスポンシブデザイン、レガシーブラウザ、SNS連携)やPL/SQLのバッチ作成などの成果のみを殊更強調し、サーバー側の言語についてはあまり触れないようにしたい。何故かというとVBだから。

VB.NETC#の方言」という見方も強くなっている。歴史的に見ればむしろDelphiから産まれて、JavaC++を反面教師としつつ上面を拝借した2002年に1.0が登場のC#よりも、BASICから連なるVBの方が全然古参だ。求人数的にもVBの方が多いように見える。加えて、言語仕様的にもC#で出来る事は基本的にVBでもほぼ可能だ。VBでしか出来ない(出来なかった)こともある。なのに一部にVBを下に見る向きが有るのは、それなりの理由があってのこと。そして俺も漏れなくVBは好きではない・・・どころか嫌いな言語圧倒的No.1であり、今後は二度とVBの案件を受けることはないし、自分がここ1.5年もの間VBを書き続けていたという事実自体すら黒歴史としたい。

VBC#は双方とも.NETフレームワークに基いているし、機能差は今ではほとんどない。ILに変換したあとは理論的には速度差もないはずだし、そもそも両者を混在して書くこともできる兄弟や姉妹みたいな言語だ。だけど、もしC#VBかで何かプロジェクトを始める機会があるとしたらC#を選ぶことを強く薦める。またVBが本職のエンジニアなんかが面接に来たら、真っ先に落として採用しない方がいい。逆にエンジニアからすれば、仮にC#VBの両方の案件に就ける機会が転がっているとして、単価がよほど違うなどの事情がない限りはC#のプロジェクトを選んだ方がいい。

もしあなたがVB歴の長いエンジニアだとしたら、老婆心ながらIT世界の潮流と自分との立ち位置を一度冷静に考えることを薦めたい。恐らくこれまでSierなどで常駐して働くことも多かったと思われるが、身に染みついている慣習や作法が、それこそコードの書き方という表面的なものから仕事に対するアプローチ全体まで、極めて不合理かつ非効率的なものになっており、いわゆる日本の「土方」現場でしか通用しえないような人材になっている危険性も高く早期にVB以外のエコシステムに触れてパラダイムを転換しない限り、エンジニアとして大成することの障害になりかねない。

なぜここまで俺は感情的にVBを嫌悪するのか。正直、不愉快に感じる向きもあるだろう。ただし俺は3-4年ほどVBに触れてきて、VBで作られたプロジェクトを沢山見てきたし、そもそも初めて触れた言語がVB(A)だ。これが同族嫌悪であるとか、過去の自分を否定することで今の自分は成長していると繕う虚勢だと言われてもそれは否定しない。それでもなお、一人の男がVBからプログラミングに入り、そしてVBを完全に棄てることになったのには、相応の苦悩とストレスと幻滅と失望がある。

長いエントリーなので最初に断っておくと、現時点ではVB.NETは比較的よく出来ている言語と言わざるを得ないので「VBが駄目な理由」として読むに値するのはとあと程度であり、あとは重箱の隅をつつくような難癖に等しい。またで言いたいことは、VBだけでなく一部のC#(更に言えば、JavaとかPHPとか)の現場にも大いにあてはまりうるだろうが、決してVBにまつわる100%を否定し、C#の100%を肯定する趣旨でないことは予め申し上げたい。

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  

非構造化(BASIC)・手続き型(VB)時代に蓄積された負の歴史がある

現在の静的型付け言語の潮流は、手続き型からオブジェクト指向に移行したあと、関数型言語の要素も取り込みつつ、動的言語のように冗長性を排除してよりエンジニアが気楽に書けるものに、という方向性に向かっていると言える。逆に言えば、複数の書き方ができるマルチパラダイム性が有り、冗長な書き方から簡素な書き方まで新旧のシンタックスが入り交じっているので、人によって書き方に自由と裁量があり、そこにコードの品質差が出ると言える。

VBの祖先は初心者用の言語であるBASICであるが、当時のプログラミングの主流は非構造型プログラミングであり、GoToの多用によるスパゲッティコードと言われるものが量産されていたとされる。一部で「.NET以降は別言語」ともいうが、未だにGoToOnErrorなどを残しているように、VBBASICから完全に脱却することはなく、敢えて過去の遺産を残して承継することで生き長らえているのである。

BASICは.NETになる前にVisual Basicとして手続き型言語として成熟することになり、当時の日本で広く人口に膾炙して、そこで一旦慣習と常識が形成されることになった。しかし、1998年にリリースされた6.0に於いても、オブジェクトと非オブジェクトが混在するなど手続き型言語としての側面も色濃く、(インターフェイスの実装もどきは5.0で導入されたが)クラスの継承ができないなど、同じくオブジェクト指向言語としては不完全だったC++Javaと比べてすら、完成度には問題のある言語だったことは否定できない。

今でもVBA(実質VB6.0)でプログラミングをすれば、VBという言語が、基本的に手続き型であることは理解できると思うが、.NETになっても、staticのように、メソッド内で変数の値をキープするという凡そオブジェクト指向を想定していない不思議なキーワードがある。メンバー変数に値を保持できるオブジェクトではなく、メソッド(プロシージャ)がVBの基本単位だった頃の名残だ(VB手続き型言語からオブジェクト指向型言語に移行するまでの流れはこういうサイト*1を参照)。

VB6は静的型づけ言語ではあるが、IDEも貧相であり、コンパイル時のエラーチェックは型までは見てくれなかった。その影響でハンガリアン記法(strTextBoxTextとか)が多く採用されていたと思われるが、俺が見たVB.NETを使う現場の100%は今日もいまだハンガリアン記法を採用している。軽快なリアルタイムコンパイルチェックがあり、優秀な補完機能を持つVSにおいて、ハンガリアン記法を用いる必要性は何処にあるのかとは思うが、「何が合理的か」「何が意味があるか」ということよりも、「VBはこうあるべき」という予め決まった文化と慣習が存在する。

こうしたVB.NETに至る前のVisual Basicの積年の過去の慣習と常識は、年配の人間や初心者にも受け入れ易いという正の側面があるが、無視できない負の側面の方が色濃く出てしまっている。C#でも手続き型指向のコードを書くことも可能だし残念なことにそういう現場があるのも事実なのだが、VBに関しては殊更LINQ型推論ラムダ式どころか、オブジェクト指向すら使用してコードを書くことは邪道という雰囲気すらある。コードの中心は数千行にも及ぶメソッド(プロシージャ)であり、クラスは単なるそれを囲む入れ物というのがVBの基本的なスタイルだ。

関数型でもなくオブジェクト指向でもなく一応構造化はされているが、長ったらしい。そう、かつてVBという言語がそうであったように、VBを用いる現場やエンジニアは概して冗長性指向なのである。一旦コードを書いたら全く同じことをコメントだけでなく、エクセル方眼紙で出来た自称設計書や自分の日報にも書かないといけないVBの現場も珍しくはない。しかし、コーディングに於ける冗長性は、凡そ保守性を下げはするものの、安全性を上げることはない。

ユーザ層の平均レベルが極めて低い。

これまで見たVBエンジニアのほぼ全員に当てはまる特徴は以下のものがある。

  1. 中長期的な視野が持てず、直ぐに「とにかく動けばいいんだ」というその場限りの拙速主義に走る
  2. 「良いコードを書こう」「新しいものを使おう」という志向や意識が乏しく、幼稚で稚拙で未熟で粗雑で冗漫で旧態依然のコードを書く
  3. 仕事に対するアプローチ全体が概して合理的・非効率的なものになっている
  4. 「どうあるべきか」という思考ができず、 向上意欲や改革意識に著しく欠けており、基本的に「現状維持」「問題先送り」「責任転嫁」という結論が先にある
  5. 普段からプログラミングを楽しんでいるとは到底思えない

例えば、Ruby。「Rubyこそが最高のプログラミング言語」「Rubyエンジニアがヒエラルキーの頂点」とまでは言わない。ただ、Rubyエンジニアには「良い書き方を追求しよう」という意識が高いのは明らかだろう。それは単純なコードの体裁にとどまらず、Rubyのエコシステム全体に「より効率的かつ合理的なアプローチ」を追求していく動きがある。まあ、その終局点が「Rubyを捨てる」という選択肢になることが散見されるのは皮肉だが、Rubyを書いていると、いわゆる「意識高い」系の人間の多さに感銘を受けるし、それに感化されて自身も成長していくはずだ。だからエンジニア人生の一度はRuby(もしくは他の意識高い言語)に触れることは間違いなくプラスになると思う。

VBはその正反対だ。VBほど意識の低い言語は存在しない。あまりに意識が高過ぎるのも困るが、低すぎる場合はその比ではない。ユーザー層の大半が、プログラミング初心者またはプログラミングは好きではないが仕事で仕方なくやっているだけのユーザーで構成されている。「有名なVBエンジニア」「VBで書かれた有名なソフトウェア」なんてとても思いつけないように、本当の実力者または他言語での経験が豊富なエンジニアの中で「全ての言語の中でVBが一番好き」なんて人はほぼいないだろう。VBとは、いわば「底辺」だ。

ただでさえ同じプロジェクトのメンバーにスキルが低い人間が要ると最終的には負担になるというのに、ましてVBエンジニアが構築したシステムの保守拡張を行う場合は筆舌に尽くしがたい苦痛を覚える。彼らは良いコードを書く能力に欠けているだけでなく、テスト方法、デプロイ方法、リリース方法、その他もろもろに於いて「能力もやる気も知識もない自分達が編み出した時間が掛かるし無駄も多いけど、多分誰にでもできる方法」を負の遺産として残しているので、後から来る我々はものすごく苦労することになる。

だからVBに長く浸かれば浸かるほど、その者はエンジニアとしては悪い方向にしか成長できないという警笛を鳴らしたい。初めての言語がVBだった人は、今この瞬間も垂れ流されているVBならではのバッドプラクティス・アンチパターンに感化されてしまい、それを普通だと思い込んでしまう。またVB畑のエンジニアが、他のエコシステムに流入することは「害」に等しい。言わばVB害だ(もっとも流入先がJavaPHPであれば、さしたる害も出ないのではないかとは思うが)。

実際の現場で書かれるコードの質が低い。

VBエンジニアが書く典型的な「幼稚なコード」のサンプルを示したいと思うのだが、誇張抜きでVBの現場では実際はこれより更に壮大なクソが大量に粗製濫造されており、それを完全に伝えきれないことは残念である。

'  メソッド名: DoSomethingToDoWithDatabase
'  概要 : 受け取った文字列に基づいてDBにアクセスして何かの処理をします
'  引数  : strParam 受け取ったパラメーター
'  戻り値: なし 
' 作成日: 2011/1/1
' 作成者: Taro Suzuki
'  修正日: 2014/12/23
'  修正者: Takeshi Tanaka (#366 No.3の処理追加)
' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
' Public Sub doSomethingToDoWithDatabase(Byval strInParam As String, 
Public Sub doSomethingToDoWithDatabase(Byval strInParam As String, _ 
                                       ByRef strOutMsg As String)
' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/

    Dim strProc1 As String = "1"
    ' /------------- 2014/10/20 #205 No.2の処理削除 Taro Yamada del start
    ' Dim strProc2 As String = "2"
    ' 2014/10/20 #205 No.2の処理削除 Taro Yamada del end ---------------/
    ' /------------- 2014/12/23 #366 No.3の処理追加 Hanako Tanaka  add start
    Dim strProc3 As String = "3"
    ' 2014/12/23 #366 No.3の処理追加 Hanako Tanaka  add end ---------------/

    ' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
    strOutMsg = ""
    ' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/

    ' パラメーターが1の場合
    If strParam Is Not Nothing And strParam.equal(strProc1) Then  
        Dim clsDbo As clsDataDbo = new ClsDbo("", Nothing, 1)
        Dim blnRet As Boolean = clsDbo.doSomething(strParam)
       ' 戻り値がTrueの場合はプロシージャを抜ける
        If blnRet = True Then
            ' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
            Dim clsMsgFunc As ClsMsgFunc = New ClsMsgFunc(456, False)
            strOutMsg = clsMsgFunc.getMsg("doSomethingToDoWithDatabase", "Success")
            ' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/
            Exit Sub
        End If
    ' /------------- 2014/10/20 #205 No.2の処理削除 Taro Yamada del start
    '' パラメーターが2の場合
    ' Else If strParam Is Not Nothing And strParam.equal(strProc2) Then
    '     Dim clsDbo As clsDataDbo = new ClsDbo("", Nothing, 2)
    '     Dim blnRet As Boolean = clsDbo.doSomething(strParam)
    '     '戻り値がTrueの場合はプロシージャを抜ける
    '     If blnRet = True Then
    '        ' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
    '        Dim clsMsgFunc As ClsMsgFunc = New ClsMsgFunc(456, False)
    '        strOutMsg = clsMsgFunc.getMsg("doSomethingToDoWithDatabase", "Success")
    '        ' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/
    '        Exit Sub
    '     End If
    ' 2014/10/20 #205 No.2の処理削除 Taro Yamada del end ---------------/
    ' /------------- 2014/12/23 #366 No.3の処理追加 Takeshi Tanaka add start
    ' パラメーターが1の場合
    Else If strParam Is Not Nothing And strParam.equal(strProc3) Then
        Dim clsDbo As clsDataDbo = new ClsDbo("", Nothing, 3)
        Dim blnRet As Boolean = clsDbo.doSomething(strParam)
       ' 戻り値がTrueの場合はプロシージャを抜ける
        If blnRet = True Then
            ' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
            Dim clsMsgFunc As ClsMsgFunc = New ClsMsgFunc(456, False)
            strOutMsg = clsMsgFunc.getMsg("doSomethingToDoWithDatabase", "Success")
            ' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/
            Exit Sub
        End If
    ' 2014/12/23 #366 No.3の処理追加 Takeshi Tanaka add end ---------------/
    End If

    ' /------------- 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod start
    ' メッセージがない場合はエラーメッセージを代入する
    If strOutMsg Is Nothing Or strOutMsg.equal("") Then
      Dim clsMsgFunc As ClsMsgFunc = New ClsMsgFunc(456, False)
     strOutMsg = clsMsgFunc.getMsg("doSomethingToDoWithDatabase", "Error")
    End If
    ' 2011/12/31 #195 メッセージを追加 Hanako Tanaka mod end ---------------/

End Sub

行数の割には内容の薄いコードだ。コードの中身よりもコメントの方が行数が多く、縦にスクロールする手間と処理の実態が釣り合っていない。そのコメントもほぼ100%が不必要なものであり、上でいう可読性を損ねたり、コピペ時の修正漏れ(上の例にも敢えて入れてあるが)などを引き起こして結果的に間違ったものになったりと、読む人間を助けるどころか、むしろ「害」となっている。

もう不要になったはずの処理が残されているのは、VBを採用をしているような現場にありがちな「流儀」だ。VBの現場でも流石に今どきGitとまでいかなくてもSVN、最低でもVSS(まじもんのゴミだが)ぐらいは導入しているところが多いのだが、これらの職場ではソースコードは基本的に削除せずにコメントアウトで残すのである。バージョン管理ソフトの存在意義を問い詰めたいぐらいだ。

コメント云々を抜いても、肝心のコードも稚拙極まりない。オブジェクト指向云々以前に明らかに手続き型のコードとしてすら洗練されていないが、VBの現場に於いては、DRYや抽象化といった概念は基本的に存在しないと見ていい。

さて、ここで「上に4番目の処理を追記してくれ」と言われたとき、できない人間は果たしているのだろうか?非プログラマーどころか小学生でも可能ではないのか。だから、こういうクソコードには、メリットを言えば「誰でも読める」という側面もあるように思える。しかし、ここでは例示するために簡素な例を示したが、実務のVB現場ではこういう冗長な記述が至る所に重複して散りばめられていて、それを調査追跡して多くのファイルを修正・・・というのは骨が折れる。むしろ「誰でも読めるが、誰がやっても疲れる」というのが実態だ。何の技術力も要らない、ただの貼りあわせ作業に等しいレベルのコーディングなのだ。

しかも悲しいことに低品質の負のスパイラルがVBだ。過去に作ったシステムを保守拡張していく場合は、どうしても既存コードに影響を受けてしまうので、VBで書かれた低品質のコードから完全に脱却することはできない。どんな優秀な人が参加したとしても、彼/彼女が最終的に産み出すVBコードは往々にして他と大差ないことが多い。クソコード過ぎてリファクタリングなんかしてしまったら、短期間で契約更新されず放出されてしまう可能性すらある。

何故なら、概してリファクタリングというのはそれに要する時間とデグレが発生してないか担保する時間を考えれば、非常に割が合わない。賢明なエンジニアは、よほどの大幅改修がない限りは、わざわざ過去の低品質コードを書き直すようなことはしない。これは日本だけでなく世界共通の真理だ。例えばGitHubでも、装飾的な変更だけでなく単なるリファクタリングは、オーナーやコラボレーター以外からプルリクエストされた場合、リスク回避とチェックする手間からクローズされることも少なくない。

まして、VBを採用する現場がリファクタリングの工数なんかを見積もるはずもないし、未だに自動化された単体テストも導入していない現場が圧倒的大多数なので、リファクタリングによるデグレーションのリスクは極めて高い。VBで書かれたソースコードのほぼ全てが稚拙で冗長でただの負債になっているのは、結局は「良いコードを書く」という意識がVBエンジニアには薄く、また文化的に良いコードを産み出しにくいという側面があるからだ。

中には「きれいなコードを書きたい」というのは、お前の独り善がりな価値観に過ぎないだろう」と言う意見も出るだろうが、果たしてそうだろうか。極論を言うならば、全てのプログラミング言語マシン語(0か1)のシンタックスシュガーであり、そこを「如何に美しく、綺麗に、簡潔に書けるか」という命題を社会が追求していった結果、C言語では飽き足らず、パフォーマンスを犠牲にしてまでいわゆる高級言語が2014年の今日も未だ勃興しているのが現実ではないのか。

むしろ「より短く書きたい」「より美しいコードを書きたい」「より良いコードを書きたい」という気持ちは、本来はプログラマーの本源的な欲求であり、それこそが逆にプログラマーを成長させていく糧とすら思える。そういう志向が薄いエンジニアは、プログラマーというよりは、IT作業員とかIT土方と称するに近い。

もし、こんなコーディングとは呼び難い単純作業を長期でやっていたら人はどうなるだろう。よほど他の現場で良いコードを見慣れており審美眼がある人間ならともかく、経験の浅い人がこの類のコードを毎日見ていたら「プログラミングとはこんなもの」という誤った勘違いを起こしかねないし、その後のキャリアに間違いなく悪影響が出るだろう。

「人月いくら」の稼働商売と極めて相性が良く、クソコードを助長させる社会抑圧がある。

VBC#の差については、海外でも当然のように話題になるし、VBがビギナー向けの言語でC#より機能が少ないという偏見はあるが、特にユーザーのレベル差を指摘する声は全く見ない。であれば、日本社会の醜悪さとIT業界の負の側面のハイブリットがVBを駄目にしたのではという推定もできる。更に言うと、VBこそ日本のITの駄目さを物語るものではないのか。日本のITの負とは、言うまでもなくSIerとそれに群がる無数の人売り商売の零細企業のことである。

福島第一原発事故という世界から注視されている現場ですら、多重派遣や偽装請負が横行し、過酷な作業を引き受ける作業員が受け取る正当な対価(それも国民の税金を源泉としている)をピンハネすることに躊躇しないのが日本人だ。IT業界も同様で、今日もまた多数のIT技術者(とは名ばかりの土方)が現場に派遣されており、ピンハネする側の懐が潤う。平均年齢が50歳に近い日本では、極少数の人間の既得損益が完全に生成されており、それが維持されるようにコントロールされているのだ。

こうしたSIerが管理する現場では「モノ」の成果とか品質ではなく、「人月いくら」で換算した工数で金を払うわけだから、当然送り出す側は「如何に多くの人を送り込み、如何に工数を増やして、如何に客にそれを飲ませて請求するか」に執着しないといけない。「何を作ったか」「どれだけ貢献したか」という純粋な技術と想像力を問うのではなくて、「どれだけ現場にいたか」という稼働に対して対価を貰うわけで、最低限の知識とビジネスマナーさえ揃っていればまさに誰でもいいって感じになっている。むしろ技術力が関係ないからこそ、異様にビジネスマナとかで揚げ足を取るSIerも多い。

VBB2B・B2Gの現場で採用が多いことも負の傾向に拍車をかける。何しろお硬い大企業や官公庁が顧客だったり、元請が日立・富士通・NTTとかの超大企業だから、とにかく形式主義と保守主義こそが至上とされる。「何が正しいか」「何が合理的か」という見解は黙殺されて「今までどうだったか」が最も強い意味を持つ。既得損益が凝り固まって、下手に歴史があるからこそ過去の大したことない実績を過度に盲信し、自分たちの立ち位置や実力を弁えるどころか、むしろ自分たちは優れた人間であるとすら勘違いする奴もいる。

となれば、冗長性とステップ数を競い合うようにクソコードを撒き散らし、可読性を下げて、保守性を下げて、将来のちょっとしたメンテナンスの苦労を倍増させることは、工数と人員を増やすという観点からは理に適っている。少しコードを書くとなったら、同じ内容をソース内のコメントどころかバージョン管理ソフト、多数のエクセル方眼紙に書いて、上の人間にレビューして誤字の一つも許さないほどの勢いで見て貰うという、一見ソフトウェア開発において極めて非効率的なアプローチも同様にまた尤も高い売上を産む。顧客がそれを看破できなければ、その1分1秒に対価が渡るのだから。

VBの現場は、基本的に「人海戦術」に依存し、目視による確認や手動に依る作業の度合いが強く作業者の負担となるような非効率的な単純作業の量が非常に多い。その理由はで述べたようにVBエンジニアは技術力や意欲が低いので効率的・合理的な手法の導入が出来ないのも大きな理由であるが、第一に非効率的な手法が是正される環境ではないのが最大の理由である。

そもそも、ピンハネ企業にとって、「綺麗に・短く・読み易く・美しく・簡潔に」コードを書かれてしまうのは凡そ背信行為に等しいのだ。仮に一部の優秀なエンジニアが集まって、高い開発効率で、より少ない人数とより少ない時間でソフトウェアを作成し、低コストによる運用と保守体制を築き上げたとしたら、それは「前例」となってしまい、顧客が今後の案件を発注する際の基準になってしまう。優秀なエンジニアが書くコードは、本人達は「敢えて難しいことはしてない」つもりでも、新し目のシンタックスAPIや使われてしまうと、どうしても不勉強なエンジニアには読みにくい。優秀なエンジニアというのは数として相対的に少ないものだし、給与も高くなるから、複数の会社がピンハネした元の金額の半分以下(時には1/5)となった報酬で引き受けてくれることはない。だから飽くまでも工数の基準は、ピンハネ企業が何十年も掛けて既成事実として水増しした、低報酬でIT業界未経験の人間や低スキルの人間をかき集めても作れるゴミの産物の「総時間」「総ステップ数」であるべきなのだ。

こういう職場では、酷いときはコードの品質や将来の保守性よりも、ステップ数(行数)で人の生産性を管理している。例えば、1時間かけてそこらの全ファイルにコペピして書き込んで大量のコードを生産する方が、1日かけて調査して設定ファイルに一行追加することで解決するよりも優れたことなのだ。将来、その大量のコピペの一つ一つを読んで修正することも想起できない近視眼的な視野しか持たない。もちろんVBの現場は必ずエクセル方眼紙の山々とセットだけど、コードを少し直すだけでエクセル方眼紙も大量に修正しないといけないし、むしろそのシート数を誇りに思うような手合いばかりだ。

何故VBで書かれてるコードはほぼ全てクソなのか。何故VBを採用している現場はほぼ全てクソなのか。その終局的なの理由はここに尽きる。誰でもいいからクソを垂れ流して一定時間席に座っていれば金が貰えるのだ。クソを垂れ流すのは楽だし簡単だし迅速だ。プログラマーでなくてもできる。既にクソをすればお金が貰えるという体制が確立しているのだから、クソ以外の産物を作ることはないし、逆にクソ以外を作りたがるような人間は早期に退場させていかないといけない。「保守性向上」「自動化」「少数精鋭」・・・これらはみんなタブーに等しく、「誰でもできる仕事」「誰でも時間と手間のかかる仕事」を手動と目視で丸一日担当させて、顧客から金を巻き上げる際に、VBという言語は非常に理に適っているのだ。



↓ここからがやっと言語の仕様的な話になる。

少なくともVB2010ぐらいまではC#と大差があった。

今ではほとんど差はなくなっているが、C#VBはもとは別言語だった。だから「C#にはなくてVBにはある」または「VBにはなくてC#にはある」という事もザラであるが、全体的にどうもVBの方が遅れ気味である。比較的大きめの機能でVBが遅れて導入したものを上げるとこんな感じになる。

VB.NETの導入時期 機能 C#の導入時期
2005 演算子オーバーロード 2001
失敗時に例外でなくNullを返すキャスト演算子 2001
continue 2001
using 2001
参照型の非等号演算子 2001
メソッドXMLコメント 2001
符合なし数値型 2001
2008 Null許容型のシンタックスシュガー 2003
三項演算子 2001
2010 暗黙の行連結 2001
yield 2005
複数ラムダ式 2008
Action型のラムダ式 2008
自動実装プロパティ 2008
コレクション初期化子 2008
配列初期化シンタックスシュガー 2008

個人的には、VBのコードの美観を圧倒的に損ねていた改行の_ が不要になるのが2010年と割と遅すぎたのが致命的だったと思う。このエントリーでもわざと改行が不自由だったときのコンパイラを前提にコード例を出している部分もある。

もちろん「C#だって大きいところでは2010年までオプション引数とか名前つき引数がなかったろ」と言う人もいるだろう。しかし、実のところ機能の実装遅れはC#よりもVBの方が遥かに深刻で影響力のあると言わざるを得ない。というのも、ただでさえバージョンアップにはリスクがあるのでプロジェクトが大規模であるほど忌避される傾向にあるが、特にVBを使う現場は事なかれ主義的なので互換性の比較的高い.NETであってもバージョンアップなんてしない。これは断言してもいい。仮にバージョンアップされたとして上に述べたようにVBエンジニアは全体的にスキルが低く、プログラミングの勉強を怠るどころかそもそもプログラミングが好きではなく仕事のために仕方なくやっているだけなので、通常は過去のコードをコピペしてちょっと変えるだけであり、追加された新APIや新シンタックスなんて使うはずがない。

等価演算子と代入演算子が同じ。

プログラミングを始めた初期は「==」に気持ち悪いものを感じた。PascalDelphiの「:=」もあまり好みではない。しかし、代入用にわざわざこういう気持ち悪い演算子が採用されたのも理由がある。というのも、VBのように等価演算子と代入演算子が文脈依存だと不都合があるし融通が利かないのだ。

例えば、VBは多重代入ができない。下のような完全に同一のコードでもC#VB#では結果が異なる。

// xとyに0が代入される
x = y = 0;
' xに(y=0)の比較結果が代入される
x = y = 0

恐らく、VBコンパイラは文と判断しないとコンパイルが通らないときは=は代入と見なし、式と判断できるときは=を比較と解釈している思われる。


なお、VBでは概して以下のようなコードが少ない印象がある。下記のような場合は、型は推論せずに敢えて指定して、わざわざハンガリアン記法まで用いてるので、blnOKという変数の中身や役割は明確であるので、意味が分からないという人は流石に少ないのではないかと思うのだが・・・。

Dim blnOK As Boolean = (hoge = fuga)

これが大抵の場合のVBエンジニアの書くコードだ。俺はワンライナー主義ではないが、普通に読みやすいコードで1行で済むところを6行で書かないといけない理由があるのだろうか(加えて、If ~ Then ... Else ... End Ifを改行せずに書けば2行で済むが、これもVBエンジニアは使わない)。VBエンジニアはステップ数で給与を貰っているような錯覚を受ける。
Dim blnOK As Boolean
If hoge = fuga Then
blnOK = True
Else
blnOK = False
End If

インクリメントとデクリメントがない

インクリメント・デクリメントがない言語は意外に多い。主要な言語でもPythonRubyScalaLuaは備えてない。理由は複数あるようだが、オブジェクト指向言語だと、数値型であっても++はスタック領域の値をインクリメントするのでなく、ヒープ領域を指してるアドレスの値をインクリメントしてしまうことになるから、という説がある。なら一応は数値型を含め全てがオブジェクトという設計思想のVBにおいてインクリメント・デクリメントが無くても問題はない・・・と言いたいのだが、C#にはちゃんとあるので、それに比べたらVBはやはり駄目ということになる。

もちろん+=を使えば、文(ステートメント)である限りは同じ文字数で同じ事はできるので、代入したいだけならそこまで差はでない。

i++;
i--;
i+=1
i-=1

但しC#メソッドの引数にそのまま増減させた評価値を返せる(つまり、式として利用できるが)、VBには代入とメソッド呼び出しという「文」「文」の組み合わせになるので、2行にするしかない。

obj.doSomething(++i);
i-=1
obj.doSomething(i)

ByValが省略できなかったし、Optionalはいまだ省力できない。

関数の引数を定義するとき、VBは常にByValByRefのいずれかの明示的な指定が必要だった。

void DoSomething(int i)
{

}
Sub Something(ByVal i As Integer)

End Sub

仮にByValを省略して書いたり、うざいから消したとしたら常にVSが強制的に補完する。マイクロソフトのサイトにもかつては「省略して書かないことをお勧めします」みたいな根拠が不明なアドバイスがあった。「いや、省略したら暗黙で値渡しにしろよ!」と常々思っていたが、MSもやっと重い腰を上げてVS2010以降は省略可能にして当該文言もサイトから消した。しかしやや遅すぎる気もする。

加えて、VBはオプション引数そのものはC#よりかなり早い段階で導入していたのだが、その代わりOptionalを常に引数の定義に付与が必須。複数のオプション引数があるとしたら、全ての頭にOptionalを記述する必要があり、しかも省略したらコンパイルが通らないというのにVSはエラーを出すだけで自動補完をしない。

この結果、C#VBメソッド定義は悲しくなるぐらい読みやすさに差がある。

void DoSomething(int i, int j = 0, int k = 1, int l = 2, int m = 3, int o = 4)
{

}
Sub DoSomething(ByVal i As Integer, ByVal Optional j As Integer = 0, _ 
	        ByVal Optional k As Integer = 1, ByVal Optional l As Integer = 2, _ 
		ByVal Optional k As Integer = 3, ByVal Optional l As Integer = 4)

End Sub

もちろん2010以降はByValと改行の_ が不要になったため今はだいぶマシになったと言えるのだが、これもVBの冗長性を助長する言語という評価を示す大きな一例だ。なお、オプション引数については下でも軽く触れているが、他にも不可解な仕様がある。

AndやOrが常に右辺を評価する

VBAndOrは他言語における&&||とは違う。というのも、VBの場合はたとえ評価中に左辺がfalseになっても常に右辺も評価する。If文の条件式が10個ぐらい複合されてるとしたら(ガチな話VBにはよくある)、最初の1つが真だったとしても残りの9個を最後まで続ける。これにより、不必要なぬるぽ(ぬるり)チェックやパフォーマンスの悪化を招く(もちろん右辺の評価にはミリ秒も掛からないことが大抵だろうが)。

if (obj != null && obj.Count > 0)
{

}
If (obj Is Not Nothing And obj.Count > 0) Then

End

「そもそも空のコレクションを返すなよ!」と言われかねない例だが、上記の場合はVBだけぬるぽ(厳密に言うとぬるりだが、このエントリーでは以降Null Reference Exceptionもぬるぽと呼称する)が発生する。流石に、これでは駄目だろうとマイクロソフトも自覚もしているので、左辺が駄目だったら途中で切り上げるAndAlsoOrElse演算子が用意されている。しかし、ただでさえ長いのに4文字長くなるし、そもそもVBエンジニアは技術も知識も乏しいのであまり使われていないのが実態。AndAlsoAndの違いを知らないというか、興味ないんだと思う。動けばいいんだし。


そもそも歴史的経緯だか互換性だか知らないが、AndやOrが両辺を評価することによるメリットが存在するのだろうか?なぜAndAlsoやOrElseを追加する前に、他の言語と同じ仕様にしなかったのか。

なお、If()が追加される前に三項演算子の役割を果たしていたIIf()関数も、両方の文を評価するという意味不明な仕様。

動的型づけを採用する場合の粒度が大きすぎる

VBにはあってC#にはなかった要素の代表的なものとして動的バインディングがあるが、C#4.0では遂にdynamicが導入された。ところがC#の場合は局所的にdynamicを用いるかどうか選択できるのに、VBの場合は少なくともファイル単位になってしまう。動的型を用いる場合の影響範囲は最小限にしたいのに、これは非常に困る仕様である。

void DoSomething(dynamic x, Object y)
{
  x.Hoge();
  y.Fuga();  // コンパイラが怒る
}
Option Strict Off

Sub DoSomething(ByVal x, ByVal y As Object)
  x.Hoge 
  y.Fuga    ' xもyも両方ともObject扱いで、なおかつコンパイラが警告を出さない
End Sub

そもそも強い静的型付け言語のC#/VBで敢えて動的型を使う局面はあまり多くないはずだし、初心者ほどVisual Studioの秀逸な補完機能に頼れる型付け機能を利用した方がいいと思うのだが、VBにはOption Strict Offを採用する思想も根強い。動的言語(RubyJavaScriptなど)の愛用者として、動的型のメリットは到底否定できないが、VBの場合は「型とかよくわかんない」「いい加減でも動けばいいんだ」程度の理由で型づけを無くしたいように見える。

なお、俺の職場にもVBのくせにいっちょまえにコード規約なんてものがあり、何か作る度にエクセル方眼紙に項目別にチェックしないといけないが(バカらしいので途中からやらなくなった)、その中に「暗黙の型変換は使用しないこと」とか書いて有る。いや、「最初からソリューションレベルで禁止しろよ!」とか思うのだが、せめて「ファイル冒頭にOption Strict Onと書くこと」ぐらいの記載にして欲しいと思った。設定よりも規約よりも人眼による管理を是とするVBエンジニアらしいエピソードだ。

大文字小文字を区別しない

VSで入力しないのであれば、区別しない(インセンシティブ)なのには正直無視できない利点がある。Shiftキーを打たなくて済むとかCaps Lockのまま打っても既にタイプしたキャピタイラゼーションに戻してくれるから楽とかに加えて、全てのプログラマーにとって起こりうる、特に動的言語では検出しにくい大文字と小文字の打ち間違いのミスがなくて済む。

ただしVSであれば補完してくれるから打ち間違いのリスクは減るし、一方で、プロパティとその値を保持するメンバー変数の命名がやや厄介になるので_とかmとかメンバー変数にはつけたりする必要がある。

class Hoge {
  private int foo;
  private string bar;

  Hoge(int foo, string bar)
  {
    this.foo = foo;
    this.bar = bar;
  }

  int Foo
  {
    set{this.foo = value;}
    get{return this.foo;}
  }
}
Class Hoge
  Private _foo As Integer
  Private _bar As String

  Sub New(ByVal foo As Integer, ByVal bar As String)
    _foo = foo
    _bar = bar
  End

  Property Foo As Integer
    Get     
      Return _foo
    End Get
    Set(ByVal value As Integer)
      _foo = value
    End Set
  End Property

End Class

個人的にはC#の方が綺麗だし、C#の区別する仕様に軍配が上がると思う。

余計な演算子やキーワードがある

VB2005でやっと演算子オーバーロードが実装されたことは述べたが、それで全て解決した訳ではない。ユーザが自分で演算子を定義する局面は多いものではなく、むしろ余り定義するべきでははないとする意見すらあるが、基本的なクラスの演算子に関しては、言語設計書が草案段階で標準で定義する必要がある。文字列型が==演算子では比較できずequal()を使わないといけない某言語が反面教師だ。

VBの場合は値型と参照型の場合の比較演算子が異なる。値型はスタック領域を比較して、参照型はポインタのアドレスが同じところを指してるかどうかを判定する挙動の違いがあるから、一律=は適用できなかったのが理由と解される。

// 構造体
if (1 == 2 || 1 != 3) {
}
// オブジェクト
if (foo == bar || foo != baz ) {
}
' 構造体
If 1 = 1 Or 1 <> 3 Then
End If

' オブジェクト
If foo Is bar Or foo IsNot baz Then
End If

また、VB6時代からの遺産を利用しているという側面もあると思うのだが、VBはイベント関係でよく分からん演算子やキーワードを使わされるところで、C#オーバーロードされた+=だけで記述できる部分が散見される。

// C#は+=演算子がオーバーロードされている
Button1.Click += new EventHandler(Button1_Click);
' VBはメソッド定義に専用キーワード`Handles`で追加する
WithEvents Button1 As Button
Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
End Sub

' もしくはまた別の専用キーワードを用いる
AddHandler Button1.Click, AddressOf Button1_Click

イベントを発生させるにも何か変なものをつけないといけない。

// C#はメソッドコールと同じ感覚で書ける
Event(this, e); 
' VBは専用キーワード!
RaiseEvent Event(this, e); 

これはオーバーロードではなくてパーサの問題かもしれないが、名前つき引数を渡すときはC#みたいに:ではなく、:=妙に気持ち悪い構文を取るのはGoToのラベルの:と区別できなかったから・・・ってことはないよなあ。

// C#4.0からやっと順不問かつ任意の引数だけを渡せるようになった
obj.DoSomething(b: "bar", a: "foo")
' VB6からの遺産ではあるっぽいのだが
obj.DoSomething(b:="bar", a:="foo")

また、VB2010までSelectTypeを用いることができなかったのは、Type=演算子が定義されてなかったらしい。これも演算子オーバーロードの実装の遅れの影響かと思われる。

予約語C#より多い

VBにあるユーザーが使いかねない予約語はこんな感じ。

  • For
  • Date
  • Alias
  • Default
  • Declare
  • End
  • Error
  • Event
  • Function
  • In
  • Me
  • Step
  • To
  • With
  • Rem
  • Stop
  • Exit
  • Until
  • Loop

「いや待てよ、forとかC#にもあるだろ?」とは思うだろうが、上に述べたようにVBは大文字と小文字は区別しないので実質的にユーザが使える単語の制約はVBの方が多い。.NETはメソッド名は先頭だけ大文字であとは小文字という命名規則が一致してるので、C#ではForとかWithメソッド名に使用可能なのだ。チェーンメソッドを書くときなんかはこういう名前の短いメソッドを定義したいこともあるだろう。

静的クラスがない

C#の場合はメンバー全てがstaticではないとコンパイルが通らない静的クラスというものを作れる。なお、サンプルは実際の.NET内のMathクラスである。

public static class Math
{
  public static const Pi = 3.14;
  public static Decimal Round(Decimal d, int decimals)
  {
    return Decimal.Round(d,decimals);
  }
      
  public static Decimal Round(Decimal d, MidpointRounding mode)
  {
    return Decimal.Round(d, 0, mode);
  }
      
  public static Decimal Round(Decimal d, int decimals, MidpointRounding mode)
  {
    return Decimal.Round(d, decimals, mode);
  }
}

VBは同じことをしたい場合はコンストラクタprivateにして目視で全てsharedになっていることを確認するか、もしくはModuleで作るしかない。

Public Module Math

    Public Const Pi As Float = 3.14    

    Function Round(ByVal d As Decimal, ByVal decimals As Integer) As Decimal
        Return Decimal.Round(d, decimals)
    End Function
    
    Function Round(ByVal d As Decimal, ByVal mode As MidpointRounding) As Decimal
        Return Decimal.Round(d, 0, mode)
    End Function
    
    Function Round(ByVal d As Decimal, ByVal decimals As Integer, _ 
                   ByVal mode As MidpointRounding) As Decimal
        Return Decimal.Round(d, decimals, mode)
    End Function
End Class

しかし、Moduleの何が駄目かというと、勝手にグローバルな名前空間を汚染してしまうことだ。静的クラスであればusingで取り込み、形名を指定指定しないことには使えないが(なお、C#6.0ではusingで型名省略できるようにするらしい)、Moduleは勝手に侵入してくるし、型名を記述して書くのはあくまで任意になってしまう。

なお、VBユーザは基本的にModuleは使わない。もちろん、使わない理由は汚染云々ではなくて、単に知らないだけである。

Forループの汎用性が低い

最近のプログラミングでは、インデックスによるforループは野暮ったい印象があるのかあまり使われない印象を受ける。俺も可能な限り他の手段を探して書くのだが、パフォーマンスや汎用性はやはりforループが一番とは思う。何よりインデックス値をいつでも受け取れるというのが有り難い。ところがVBはそのforループが若干弱い。一見は「省略した場合は暗黙で1ずつインクリメンタルしてくれるVBの方がいいじゃん」と思うかもしれないが、実はVBの方が大幅に機能が制限されている。

for (var i =0; i < arr.length; i++){
  // 最大の違いとしてはC#は;で区切られた3つの部分を色々といじれる。
  // for ( A; B; C )
  // A:ループの最初に実行したい式もしくは文が置ける。
  // B:boolさえ返せば何でも置ける。典型的には何かループ終了の条件を置きたい場合。
  // C:ループが1回終わるたびに繰り返したい式や文を置ける。
}
For index = 0 To arr.length
  'Step nを省略した場合は暗黙でStep 1になる。
Next

また、上の例だとC#array.lengthはループが1回終わる度に評価されるが、VBarray.lengthの値はループ開始時にのみ評価されるので、ループ中に変化したとしてもループが終わることはない。動的に終了条件を変更することができずBreakなどを使って抜けるしかない。もっとも、VBにはExit Forなどがあって外周ループを一気に抜けられるのだが。

なお、VBエンジニアは当然LINQなんて絶対に使わないので、forループを頻繁に用いて汚いコードをよく書いていることが多い。

??演算子がない

C#の場合はNullableのオブジェクトに代入するとき、nullだった場合は別の値を代入するという三項演算子シンタックスシュガーみたいなものが存在する。

var j = i ?? -1;
Dim j As Integer = 0
If i Is Nothing Then
  j = -1
Else
  j = i
End If

まあ、三項演算子Ifを使えばVBでも一行で書ける。ただし、大抵のVBエンジニアは三項演算子なんて使わないし、そもそも「??で書けるからってなんだ」みたいな思いを持つかもしれない。まあ。あまりNullableも使わないしな。

キャストの仕様が更に面倒くさい

基本的に「キャストとは、今までAと思っていたものをBと見なす」という考えでいいと思う。 「ある哺乳類(その正体は猫)」がいるとして、それを「動物」と見るのがアッパーキャストで、より細かく「猫」と見るのがダウンキャスト。猫は哺乳類であるし動物だから、全て正しいのでこのキャストは通る。あくまでも中身(ヒープ領域)を変更するのではなく、見方(スタック領域)を変更するのだ。

C#のキャストは2種類しかない。Effective C#では「asを使え」とあるが、C#nullが代入できない値型は()でキャストしないといけず、また参照型も必ずしもasが速いとはいえないようなので、2種類の書き方が混在するかカオスだ。例外処理とぬるぽチェックのどっちが書きたいか気分で決める感じ。

var foo = (Foo) hoge;  // 失敗したら例外。
var foo as Foo = hoge; // 失敗したらnull代入。高速説も。

ところがVB#は3つある。このうちTryCastasと同じものと見ていいと思うのだが、他の2つはC#のプレフィックスキャストとは異なる。

Dim foo = CType(hoge, Foo)      ' 変換も行う。遅い
Dim foo = DirectCast(hoge, Foo) ' 失敗したら例外。
Dim foo = TryCast(hoge, Foo);  ' 失敗したらnull代入。高速説も。

このうち、DirectCastC#()より頭が硬い。

int i = 1;
short s = (short)i;
Dim i As Integer = 1
Dim s = DirectCast(i, Short) 'エラー

たとえれば、C#は「イルカサイズのクジラがいればイルカと呼んでもいいじゃないか」と言ってるのに対して、VBは「クジラとイルカは違う。どうしてもクジラをイルカと呼びたければ、クジラをイルカに変換しろ!」と言い張るような感じで、どう見てもC#の方が柔軟であり合理的だ。

CTypeはキャストではなくて「変換」に近い。見方を変えるのでなく中身を変えようとするのだ。これは静的言語でもなかった時代からの負の遺産らしいが、CTYPE("1", 1)なんてコードが通るし、.NETではなくてVB特殊の実装らしい。基本的にはVBではDirectCastTryCastのみを使い、どっちを使うかは気分で選べばいいと思う。しかし、C#()asシンタックスが違うので個人の美観にもよるのだが、VBはどちらも関数みたいな演算子なので面白みがない。

VSのシンタックスハイライトがC#より制限されている。

これはMSの怠慢なのかもしれないし、歴史的経緯()とかいうやつなのかもしれないが、代表的なところではC#だとクラス、インターフェイス、構造体、列挙型の全てが色分けできるがVBは全て一色である。ただでさえC#より文字の圧迫感が強くて美しくない字面になりやすいというのに、こういう所でも差をつけられていては、VBで美しいコードを書きたいという意欲が失せるというものだ。

もちろん、MSの中の人が「VBエンジニアはどうせインターフェイスや構造体や列挙型なんて使わないだろ」と思ってこの仕様にしたのかもしれないし、その可能性は完全には否定できない。というのも、俺はVBで書かれたコードで、自分達でインターフェイスや構造体、列挙型を定義してるところは一度も見たことがない。

変数の暗黙の初期化が惜しい

C#はローカル変数に対しては宣言と同時に代入が必要であるが、VBは歴史的経緯からか宣言だけでもよい。その場合は、自動的にデフォルト値を設定してくれたりする。ただ、半分の行で済む代入&宣言処理をわざわざ倍の行で書く阿呆も多いので、デメリットといえばデメリットにもなっているかもしれない。

var x = 100
var s = "foo"
Dim x As Integer
Dim s As String
x = 100
s = "foo"

これによりC#が参照渡しする引数を初期化せず渡す場合のoutとか細かいキーワード追加をしてる傍ら、VBはそのまま渡せたりもする。

ただし、微妙に惜しい部分がある。例えば、Stringの初期値はEmptyでなくNothing(null)なので、C#と同じく、IsEmptyが使い物にならず、String.IsNullOrEmpty()を使わないといけない(「Stringもオブジェクト(参照型)なんだし、他と同じ動作にしないといけないだろ!」と思う人は、文字の比較に==ではなくてequalを使わないといけない某言語でもどうぞ)。

文字列はオブジェクトと言ってもやや特殊なので初期値をEmptyにしていたらだいぶ快適だったと思う。もちろん.NET全体でNothing(null)はオブジェクトではないという扱いなので、VBだけでなくC#の欠点でもあるのだが、折角初期値なんてものがあるVBはとりわけ「勿体無い」という思いがある。

' objがインスタンスを参照してるとは限らないので大抵の場合これは使えない
If obj.IsEmpty Then

End If

' これが安牌
If Strings.IsNullOrEmpty(obj) Then

End

また、ローカル変数に初期値があるならば、どうしてオプション引数にデフォルト値の設定が必要なんだろうか? C#のようにデフォルト引数の有無で省略可能にしているなら分かるが、前述したようにVBの場合はいちいち全ての引数の頭にOptionalをつけないといけない訳で・・・。.NET以前からある仕様だから過去の負の遺産なんだろうけど、完成度という意味では惜しい。

Sub DoSomething(Byval Optional i As Integer)
  ' デフォルト値を書いてないのでエラーが出る
End

三項演算子の出来が悪い

VB6からIIf()関数があったが、遂に2008年にIf演算子が導入された。ところが演算子なのに関数みたいなシンタックスを採用しているので三項演算子の美しさみたいなものが微塵にも感じられないものにはなっている。一応型安全だしネストもできるのだが、型推論が壊れており、下のケースはC#だとコンパイラが通らないがVBだと通ってしまう。

// 最後が数値型なのでエラーになる
var x = (foo) ? "hoge" : (bar) ? 'fuga' : 1;
' VBコンパイラはこれを検出できない。
Dim x = If(foo, "bar", If(bar, "fuga", 1))

インターフェイスの実装は常に型名とメソッド名が要求される

C#インターフェイスで実装するメソッドは、実装元と同名にしないといけないという制約がある。たまたま複数インターフェイスが同名のメソッドを持っていたりするが実装は別にしたい場合は型名を明示することで分けることができる。Go言語の「同名のメソッドを持っている限りは同じ型と見なす」という思い切りの良さはないが、C#は実に合理性に溢れる仕様だ。

class ImplementationClass : ISampleInterface
{   
    // ISampleInterface.SampleMethod()と明示的に書いてもいい 
    void SampleMethod()
    {
        
    }
}

それに比べてVBメソッド名が同じだろうが異なろうが常にインターフェイス名を明示しないといけない。これはVB5.0で導入されたインターフェイスの実装方法を引きずる歴史的経緯なのかもしれない(もちろんVBはその代償として実装先でメソッド名を変更できたりもするが、これに果たして大きな利点があるのだろうか。元のインターフェイスが余りにどうしようもない命名をしていて変更したいのであれば、C#でも別名のメソッドにラップすれば同じことができる)。

Class ImplementationClass 
    Implements ISampleInterface

    Sub Hoge() Implements ISampleInterface.SampleMethod
      ' インターフェイス名を明示的に書かないとコンパイルエラー
    End Sub
End Class

ただ、某言語には@Overrideと、コンパイラの都合とかではなく、可読性のためだけに任意でインターフェイス由来のメソッドということを示すためのアノテーションがある。VBはそれを強制し、読む人間が分かりやすくしたものとも言える。C#インターフェイスメソッドoverrideキーワードをつけるのは禁止しているが、インターフェイス名とメソッド名を任意で書くことは許しているので、C#は冗長性を廃して任意とし、VBは冗長性を取り必須としたという立場の違いの話かもしれない。

volatileがない

個人的には、volatileは単なるおまじない程度に考えているが、lockinterlockと違って、VBではなぜかキーワードとして用意されていない。なので、いちいちメソッド内でThread.VolatileRead()VolatileWrite()を呼ぶしかない。

複数行のコメントができない

C#//の単一行コメントだけでなく複数行コメントができる。ただし俺はどの言語でも複数コメントアウトは使い勝手があまり良くないので、これについては殊更VBの欠点として語るつもりはない。多分このままでも十分だから放置されているんだと思う。逆にCSSみたいに/* */しか用意されていないという悲惨な例も。

/*
 コメントです
*/
'
' コメントです
'

なお互換性を考えてかVBにはREM(「Remark」の略だというのが通説)というキーワードも用意されているが誰得なんだ。さっさと廃止していいだろう。

何故か特定の場面でメソッド呼び出しが文扱いされない。

基本的にメソッド呼び出しや代入は文になるはずだが、VBの場合インスタンスを作ると同時にメソッドを呼び出すことはなぜかできない。

new Hoge().DoSomething();
Dim hoge = new Hoge()
hoge.DoSomething()

これを回避するにはVB6時代の遺産であるCallを使うしかない。

unsafeがなく、生ポインタをイジれない

これが一番大きいと感じる人もいるだろうが、個人的には実務では一度も使ったこと無く、その立場で「VBはこれがないから駄目」というのはフェアではないので比較材料としては適切と思わない。いつか某言語とC#を比較するときにもこれは触れなかった。ともかく、C#はCの名前を冠していることもあり、メモリを直接操作できる。

恐らく画像映像処理などパフォーマンスが問われるものを実装したいのであればVBでなくC#になるだろう。実際C#はUnityやPS4のゲーム開発などでも使われてるけど、VBは・・・。

unsafe static void Main(string[] args)
{
  double pi  = 3.14;
  double * pip = &pi;
}

・・・あとはまあ、他にも無数に細かい言語仕様の違いもあるだろうが、俺は言語仕様の研究者でもないので全ては挙げられないし、これ以上無理に言語仕様の欠点をあげつらおうとすればするほど、改めて今のVBには隙はないなと感じてしまう。最近はTypeScriptにお熱らしいが、マイクロソフトC#VBの差異を無くそうとする尽力には驚きだ。

最近の言語の潮流を見れば、文末の;が不要なVBの方が手動で記入を強いるC#より理に適っている。SwitchC#selectよりまともな仕様だし、引数なしのメソッドは()が省略できるのも好きだ。あまり使わんが引数つきプロパティもある。2010年までC#にも実装が遅れていた要素をあげつらえば「VBC#より上」という意見も出せる・・・かもしれない。結局は言語自体は悪くないんだよな。使う人と社会が悪すぎる。

総論

VB.NETは非常によく出来た言語である。C#と比較しても機能性に遜色はない。しかし、VBを採用している現場でまともなコーディングができることはないだろう。というのも、VBの現場で見られる文化・哲学は、今日のソフトウェア工学における理想のそれと見事なまでに対照的だからだ。

理想 VB
オブジェクト指向 (+関数型) 冗長性指向手続き型
Don't Repeat Yourself Write Everything Twice
関心の分離 関心の集約
(例:全てをコードビハインドに)
人はミスをする 人はミスをしてはいけない
無駄なものは消せ / KISS カーゴ・カルト
自働化 手動・目視・人海戦術
抽象化
if (person.isActive)
具象化
If objPerson.strActiveStatus = "1"
There's only one way to do it.
There's more than one way to do it.
There's only one oldie way to do it
フルスタックエンジニア 自称ベテラン、クラサバ専門家
自社開発 受託開発、客先常駐、偽装請負、多重派遣
能力主義 年功序列、中抜き、ピンハネ、人月単価

もちろん、C#(ついで言えばRubyPythonScalaもGoも)を採用していても良くない現場はあると思う。でも最初に書いた通り、VBを使っている現場は99.99%がクソだ。もしC#で書かれたプロジェクトでクソコードやアンチプラクティスが実践されていたら、それは多分C#の文化というよりは、VB畑の人間が作ったC#プロジェクトなんじゃないかなと思う(もし文字列の比較にEqual()が使われていたら間違いなくJava畑だろうけど)。

VBAからVB.NETにかけて、Visual Basicという言語には長いこと付き合ってきた。VBという言語を好きになろうと頑張ってきたし、今でも言語そのものの完成度は認めざるを得ない。なんとか過去に書かれたクソコードやクソ習慣を変えようと頑張ったりしてきたけど、最終的には諦めて、VBには幻滅して、二度とVBだけは書かないと決めた。なぜなら、VBでコーディングをすることは、プログラミングを嫌いになることに等しいからだ。

コーティングを楽しみたい。 VBはコーディング作業がつまらない。
新しいことをどんどん覚えたい。 VBは新しいことは覚えられない。
自分のアイデアと創造性をコードで描いて届けたい。 VBは型にはまったコピペコードを強制される。
サービスや製品の機能や仕様を考えて提案したい。 VBは作業員が提案や着想はできない。
自分の技術とセンスを評価して貰いたい。 VBは年功や多重請負での自分の階層位置が評価を決める。
合理的のためなら時には過去の慣習なんて捨てたい。 VBは形式と過去の慣例が再重視。
実力に見あった高い給料を貰いたい。 VBは高い給料は貰いにくい。


人が二度とVBは書かない理由としては十分過ぎるだろう。

c#6.0では複数の戻り値を返せるようになる(かもしれない)

前回こんなエントリー書いた矢先に新しいコンパイラ(Roslyn)が登場、しかもオープンソース化されるという一報が飛び込んできた訳だが、公式サイトに書いてあるこの3つは間違いなくC#6.0に入ると見て間違いないと思う。

  • Primary constructors (public class Point(int x, int y))
  • Declaration expressions (s.TryParse(out var x))
  • Indexed members (json.$x)

んで、相変わらずまともなソースがなくて申し訳ないが、海外版のWikipediaにはこんなリストがある。

  • Succinct syntax for primary constructors
  • Declaration expressions
  • Indexed members
  • Import of static type members into namespace
  • Exception filters
  • Index initializers
  • Await in catch/finally blocks
  • Readonly properties
  • Property expressions (property lambdas)
  • Method expressions
  • Parameter arrays for IEnumerable interfaces
  • Null propagator (Succinct null checking)
  • Multiple return values
  • Constructor type inference
  • Syntax for writing binary numbers and large decimal

大半は既に噂されているものなのであるが…

Multiple return values
Multiple return values
Multiple return values
Multiple return values
Multiple return values

f:id:Cedille:20140411200806j:plain

やっと来たか!

実はこれ、C#だけでなく他の言語でも何でできないのか非常に疑問に思っていた部分である(確か関数型言語ではできるのもあったかもしれない)。コンパイラなんてとても書いた事ないが、実装的にもパーサを多少弄るだけで、インラインで複数変数を宣言できる言語であれば、そう苦労せずに実装できそうに思えてならないのである。

ただ、RubyとかJavaScriptみたいな動的言語であれば、順番に従わないといけない複数の戻り値を扱うより、適当にハッシュなりオブジェクトを返した方がいいってことで、あまり導入する必要性ないかもしれないなーというのは分かる。でもIDEの強い補助を享受できて、なおかつハッシュの使い勝手が悪い静的型付け言語ではこれはあって然るべしだと思う。

シンタックスはこんな感じだろうか。

private int, string, Object getValues() {
  return 0, "", null;
}

var a, b, c = getValues();


…つーか、よくよく調べてみたらScalaでは既にできるらしい。