Magnolia Tech

いつもコードのことばかり考えている人のために。

プログラミングを難しくする要素って何だろう

以前noteに書いた記事からの転載 エクスポートできないので、定期的に少しずつ転載していきます。

いつかちゃんとしたスライドに書き起こしたいとおもいつつ、まだ手がついていないけど、この記事に書いている「プログラミングは、コードと、データと、改修の歴史の3つの要素が絡み合う」を分解していきたい。

コードと、データは本質的には不可分だし、その結びつきを分解できないように密に結合させているのが、改修の歴史なんだ

よく「データの寿命はコードよりも長い」と言われるけど、受け継がれたデータは、当たり前だけどそれが作られた当時のコードに強い影響を受けていて、不可分だし、暗黙のうちにコードの特性を引き継いでいる。

つまり、例え直接的にはコードが無くなったとしても、コードの影響が無くなるわけではない。

そして、それらの蓄積が歴史となって、全体を形作っていくんだ。

だから、データとコードの寿命は同じくらい長い、と言えるんだ。


プログラミングは難しい…たぶん。

オブジェクト指向プログラミング、関数型プログラミングアジャイル開発手法、各種設計原則や、テスティングフレームワークを使ったTDD等々…色々なプログラミングを支える要素技術はここ10年で爆発的に進化して、「とりあえず動くものを作る」という段階から、「先を見据えて、ずっと維持できるものを作る」という段階に変わってきたように思える。

きっとそれはシステムが動く環境が変化し、以前のような「動いているものを触るな!」という思想ではとても維持できなくなって、「なにもしていないからこわれました」という時代に変わってきたからだと思っている。

とはいえ、じゃあオブジェクト指向プログラミング言語を覚えて、各種設計原則を頭に畳み込めば魔法のように解決するか?と聞かれればやっぱりそんなことは起きない。

なぜか?

定量的な情報は探しても見つからなかったけど、感覚的にコードは書いている時間より読まれている時間の方が圧倒的に長い、ということに異論は無いと思う。たいてい、プログラムの寿命から言って、新規にコードを書くより既存のコードベースに対して改修を加えていくことが多い。

その時に考えることは、以下のようなことだと思う。

  • 今のコードは何をするのか、どうゆう意図か、理解できたか?
  • 今回、修正すべき箇所はどこか、漏れなく抽出できたか?
  • 既存のコードと、今回の修正コードは整合しているか?
  • 次回修正する際の障害となるような一貫性の欠如は無いか?
  • 上記の情報は、後で追跡可能なように、まとまっているか?

ベストプラクティスとして、影響箇所が局所化されるように疎結合に作っていく、改修漏れが起きないように一貫性を保って作っていく…原則は分かっていても、現実の要求に照らし合わせていくと、必ずしも全てを満足することはできない(満足することができないからこそ、原則に立ち返るのは大事だけど、それはまた別の話)。

ここで唐突に結論めいたことに突入するのだけど、プログラミングは、コードと、データと、改修の歴史の3つの要素が絡み合うと思っていて、結局コードだけ見ても分からないし、データを追加しても分からないし、これまでの改修の歴史まで見て初めて分かることが多い。

いくらテストコードにデータパターンが網羅されていても、結局git blameで改修の歴史を追いかけたことは皆さん有るはずだ。

そして、これらのテクニックは非常にプロジェクトローカルなバッドノウハウになりがちで、技術書や、スクールでも扱われない…みんなゼロからきれいなコードを書くこと、きれいな設計にしないと後で困るよ、という事は言ってくれるは、現実にそうではないコードとの向き合い方は教えてくれない。

リファクタリングの技術は、解説された本も有るけど、きれいではないコードをリファクタリングする前に、まずは理解して直すだけ、というシチュエーションの方が多い…そんな時に、どうすればいいのか?

そんな時に、プログラミングを難しいもの…と感じるのではないか?

ということで、ここではプログラミングを難しくする要素は、既存のコードベースを読み解くことです、しかもコードだけでは駄目で、データも、歴史も見ないと分からないことが難しくしているのではないか?ということを書いてみました。

この辺、もっと事例を挙げてスライドにいつかまとめて発表したいと思っている。


twitter.com

JSONのStringのエスケープ方法

今さらですが、JSONにおける文字列のエスケープ処理について調べたので、そのメモ。


JSONの規格は現在では幾度かの改定を経て、RFC8259にまとまっている。

https://tools.ietf.org/html/rfc8259

文字列については「7. Strings」にまとまっている。あまり長くないので、順番に全部見てみよう。

The representation of strings is similar to conventions used in the C family of programming languages. A string begins and ends with quotation marks. All Unicode characters may be placed within the quotation marks, except for the characters that MUST be escaped: quotation mark, reverse solidus, and the control characters (U+0000 through U+001F).

必ずエスケープされなければいけない文字として、quotation mark(")と、reverse solidus(\)、the control characters (U+0000 through U+001F)が指定されている。

JSONにおける文字列はquotation markで囲まれ、エスケープreverse solidusを使うからですね。あとは、画面に表示できないthe control characters(改行とか、タブとか)が指定されている。

エスケープされなければならないthe control charactersの範囲としてU+0000からU+001Fまでしか定義されていなくて、DELを示すU+007Fが指定されていないのは何故だろう?

ちなみに、DELがU+007Fに用意されている理由、今回調べて初めて知った。面白い。

https://ja.wikipedia.org/wiki/ASCII

ASCII 127(全てのビットがオン、つまり、2進数で1111111)は、delete(削除文字) として知られる制御文字である。この記号が現れた場合、その部分のデータが消去されていることを示す。この制御文字だけ先頭部分になく最後にある理由は、パンチテープへの記録は上書きが出来ないため、削除する際には全てに穴を空けることで対応できるというところからきている

続いて、エスケープの表記方法へ続く。

Any character may be escaped. If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), then it may be represented as a six-character sequence: a reverse solidus, followed by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. The hexadecimal letters A through F can be uppercase or lowercase. So, for example, a string containing only a single reverse solidus character may be represented as "\u005C".

Unicode基本多言語面(the Basic Multilingual Plane (U+0000 through U+FFFF))は、どれもエスケープ表現OKで、「バックスラッシュ」+「u」+「4文字の16進数」で表現される。

16進数を表すのに使うAからFは大文字でも小文字でも可。

Alternatively, there are two-character sequence escape representations of some popular characters. So, for example, a string containing only a single reverse solidus character may be represented more compactly as "\".

それとは別に、いくつかのメジャーな文字については、2文字でのエスケープ表現が用意されている。おなじみのバックスラッシュで始まる表記。

コード 文字 2文字でのエスケープ表記 6文字でのエスケープ表記
U+0022 quotation mark \" \u0022
U+005C reverse solidus \\ \u005C
U+002F solidus \/ \u002F
U+0008 backspace \b \u0008
U+000C form feed \f \u000C
U+000A line feed \n \u000A
U+000D carriage return \r \u000D
U+0009 tab \t \u0009

通常文字列の中に入る制御文字といえば、タブと改行くらいなので、UTF-8が取り扱える環境であれば、ほぼ6文字でのエスケープ表記は不要になるように、イイ具合に対象の文字が選定されているのが分かる。

ここで一つポイントなのは、いくつかの実装でsolidus(スラッシュ)がエスケープの対象でなかったり、オプションだったりすること。JavaScriptの規格であるECMA-262ではエスケープ表記の対象にsolidus(スラッシュ)は含まれていなかったけど、最初の独立したJSON規格であるRFC 4627以降では対象に含まれている、という違いが有る。

正規表現の開始と終了の文字としてよく使われることからエスケープ対象になったのか……正確な経緯はわかりませんでした。ご存知の方、教えてください。

また、JavaのStringクラスもスラッシュを除くと、同じ2文字表記のエスケープを扱える(6文字の表記も扱える)。しかし、スラッシュの2文字表記は扱えないので、注意が必要(エラーとなる)。

To escape an extended character that is not in the Basic Multilingual Plane, the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. So, for example, a string containing only the G clef character (U+1D11E) may be represented as "\uD834\uDD1E".

基本多言語面を越えるものはUTF-16サロゲートペアに該当するものは12文字で表現される。


では、おなじみPerlでの実装を見てみましょう。

https://metacpan.org/release/JSON-PP/source/lib/JSON/PP.pm

エスケープに関する変換を行なっているのは、string_to_jsonという関数です。

my %esc = (
    "\n" => '\n',
    "\r" => '\r',
    "\t" => '\t',
    "\f" => '\f',
    "\b" => '\b',
    "\"" => '\"',
    "\\" => '\\\\',
    "\'" => '\\\'',
    );

sub string_to_json {
    my ($self, $arg) = @_;
 
    $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g;
    $arg =~ s/\//\\\//g if ($escape_slash);
    $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg;
 
    if ($ascii) {
        $arg = JSON_PP_encode_ascii($arg);
    }
... 

    if ($utf8) {
        utf8::encode($arg);
    }
 
    return '"' . $arg . '"';
}
  • 最初に、2文字形式のエスケープ処理する文字を優先的にreverse solidusでエスケープ
  • 次に、solidusがエスケープ対象と設定されていれば、2文字形式でエスケープを実施
  • 続いて、必ずエスケープが必要なthe control characters (U+0000 through U+001F)のうち、先の二つのステップでエスケープ処理をしていない文字を6文字の形式でエスケープ
  • 最後に、文字コードに合わせて処理の振り分け…US-ASCIIだけで表現したい場合は、U+0080以降の文字を全て6文字の形式でエスケープする
  • サロゲートペアに該当する文字は、12文字の形式でエスケープ
*JSON::PP::JSON_PP_encode_ascii      = \&_encode_ascii;

sub _encode_ascii {
    join('',
        map {
            $_ <= 127 ?
                chr($_) :
            $_ <= 65535 ?
                sprintf('\u%04x', $_) : sprintf('\u%x\u%x',_encode_surrogates($_));
        } unpack('U*', $_[0])
    );
}

sub _encode_surrogates { # from perlunicode
    my $uni = $_[0] - 0x10000;
    return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00);
}

サロゲートペアの場合の、UTF-8からの変換方法がスマートだ。

ただし、JavaScalaなどの言語では、最初からUTF-16のコードポイントで取り出すので、1コードポイントずつ変換していけば良い。


意外と知らないことが有って調べてみると、学びが有った。

充電用のUSBケーブルの長さについて考える

充電用のケーブル、いざ買おうと思うとユースケースによって、必要な長さが全然違うし、だからと言って一番長いやつ!ってのもアレだったりするので、適切な長さについて考えた。

属人性をどう捉えるか?

「職場の属人性を排除して、誰でも同じ作業ができるようにしよう」みたいな話、よく話題になる。

でも、そのタスクのフェーズによって「属人性」が表す意味は変わってくる。

その作業が、その組織にとって完全にコモディティ化していて、誰でもできないと困る作業(会社のルールとして明確に定義されている事務手続きとか)は、特定の人しか知らない、できない、というのは確かに好ましくない。また、その作業自体が、一定以上の量が有り、多くの人数を揃えないといけないような特性が有る場合も、ムラが起きないようにしないといけない。

だけど、その人しか生み出せない”価値”に対して「属人性」というラベルを貼るのは違うんだけど、もう一方でその価値が一度発生すれば、ずっとそのままか?というそれも違ったりする。

また、別の価値を生み出すためには、どこかでそのことを他の人に委譲する時が必ず出てくる。

「属人性」という言葉は、割とネガティブに使われることが多いし、強い言葉なんだけど、うっかり尊重しないといけない「価値」に対して使わないように気をつけないとなって思った。


追記:onkさんのツイートをみて、ハッとした、それだ!

もう「自動化」って言わなくてよくないですか

以前noteに書いてた記事の転載

—-

なんとなく、「自動化」って言葉のニュアンス、“今の状態にわざわざ苦労して付け加えてやること”みたいな、特別感出していませんか。

それより、わざわざ”手作業”でやる方を「手動化」って言った方が、よくないですか?

「え?それ手動化でやるんですか、まだまだ人間じゃないと判断できない価値が有る作業なんですねー」みたいに。

どっちを特別視するか?って時に、より「当たり前の方」にはわざわざ名前つけないですよね。特別だと思っているから名前付けるので…だから、手作業の方に名前を付けた方が、その価値に気づきやすいんじゃないかって。

手作業の方が、直接的な作業時間以外にも、さまざまな意味でのコストがかかっていて…例えばどんな些細なことでも「判断する行為」は人の関心量を削って行くんですよ。

そもそも「自動化」の価値って、やってみると案外”自動でやってくれる”ことより、「作業フローが可視化される」「判断基準が明確になる」みたいなところだったりしますしね。そうすることで属人性を排除すべきところと、そうでないところが明確になるんですけど、これ自動化じゃなくてもやらないといけないことですよね。

基準が明確になってしまえば、作業が分離できるんですよ。

任せられる。

人の関心量には限界が有るので、少しでも関わった以上は、その関心量の消費が必ず一定量有るんですよ。その直接的に手を動かした以上に、関心量の消費が色々な所に効いてくるんです。なのでとにかく関心を分離して、任せられる状態になっていることに価値が有るんじゃないですか。

現代的なプログラミングの基本は、「関心の分離」です。より集中すべき所に集中すべき時に、まずは作業じゃなくて、関心を分離する方法を考えるといいんじゃないかって。

自動化でも手動でも大事なことは変わらない訳です。そこに気づくために、あえて手作業こそを特別なこととして、「手動化」って呼んでみるといいんじゃないかと思った話です。

Scalaでバイトコードに出力されるparamterにドットが含まれることへの対応

docs.oracle.com

4.2.2. Unqualified Names

Names of methods, fields, local variables, and formal parameters are stored as unqualified names. An unqualified name must contain at least one Unicode code point and must not contain any of the ASCII characters . ; [ / (that is, period or semicolon or left square bracket or forward slash).

なるほど、仕様書にそう書かれているのか。

先日のエントリでなぜそんなチェックをしているのか分からないと書いたけど、Javaの仕様書には書かれていなかった、JVM側の仕様に起因していた。バイトコードだからそりゃそうか。

blog.magnolia.tech

Scala側にissueをあげた

It is possible to set a parameter name to a method parameter that is illegal in Java · Issue #12306 · scala/bug · GitHub

そうしたら、早速PRが作られた。JVMの仕様と合っていないから、直すのが適切だろうと。

Parameter names are encoded by som-snytt · Pull Request #9435 · scala/scala · GitHub

なにげなく立てたけど、改善に役になってよかった。

ちなみにDotty(Scala 3.0.0)はバイトコードにパラメータ名を設定しないことが分かった。issueをあげようかな。

Re: 一冊の本をじっくり読み込み、知識を吸収するためにはどうすればいいのか

blog.magnolia.tech

先日、公開したエントリがわりと反響が有って、かなりのブクマを集めた。そして、ブクマのコメントで皆さんの色々な読書法が集まり、まさに”集合知"という感じで知見が得られたのが有り難かった。

b.hatena.ne.jp

ただ、前回のエントリの冒頭にちゃんと書いておけばよかったと思ったのが、自分もすべての本をあのような読み方をしているわけでは当然なくて(時間がいくら有っても足りない)、特に技術書などで「これはじっくり読みたい」と思えるものを、どうやって中断せず、関心を継続し、深く読み込むか?という観点で書いたエントリなのです。

また、何人かの方が言われているように「何度も読む」という方法は定番ですが、自分には向いていなかったですね(最近の技術書、分厚いので何度も読めないですしね…)。

人それぞれいろんな読み方が有りますね。


いろいろな読書法に関する書籍を教えていただいたので、こちらも読んでみたい、と思いました。どれも全然知りませんでした。

知的戦闘力を高める 独学の技法

知的戦闘力を高める 独学の技法

学術書を読む

学術書を読む

また、「解釈学的循環」という言葉が非常に興味深かったですね。

解釈学的循環 - Wikipedia


引き続き、こうやって読み込んでいる!というやり方があれば教えてください!

コメントやブクマ、Twitterへのリプライ、DMでも大丈夫です!

twitter.com