Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

康煕字典体から常用漢字へ変換するコマンドレット

変換対照の文字は、文化庁 常用漢字表*1康煕字典体が示されているものを対照とした。常用漢字表のPDFの内容をテキストファイルへダンプし、以下のスクリプト常用漢字とカッコ書きの康煕字典体とのペアを抽出した*2

> Get-Content .\常用漢字表.txt | Where-Object { $_ -match '(?<Oyaji>.?)   ((?<Itaiji>.?))   ' } | Foreach-Object { ("{0}{1}" -F $Matches.Oyaji, $Matches.Itaiji) }

上記の結果をもとに、trコマンド相当のコマンドレット*3を使って変換処理を実装した。

function tr {
  Param(
    [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
    [string] $TargetString,
    [Parameter(Mandatory=$true)]
    [string] $FromString,
    [Parameter(Mandatory=$true)]
    [string] $ToString
  )
  begin {
    # [-split ""] splits a surrogate pair into two invalid characters,
    # so the code below is not suitable for this purpose
    # $FromStringArray = $FromString -split "";
    # $FromStringArray = $FromStringArray[1..($FromStringArray.length-2)];

    # Split string into character array
    $FromStringArray = @();
    $FromStringBytes = [Text.Encoding]::UTF32.GetBytes($FromString);
    for ($i=0; $i -lt $FromStringBytes.length; $i+=4) {
         $FromStringArray += [Text.Encoding]::UTF32.GetString($FromStringBytes, $i, 4);
    }

    $ToStringArray = @();
    $ToStringBytes = [Text.Encoding]::UTF32.GetBytes($ToString);
    for ($i=0; $i -lt $ToStringBytes.length; $i+=4) {
         $ToStringArray += [Text.Encoding]::UTF32.GetString($ToStringBytes, $i, 4);
    }
  }
  process {
    for ($i=0; $i -lt $FromStringArray.Length -and $i -lt $ToStringArray.Length; $i++) {
        $TargetString = $TargetString.Replace($FromStringArray[$i],$ToStringArray[$i]);
    }
    $TargetString
  }
}

function ConvertFrom-Kokijitentai {
  Param(
    [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
    [string] $TargetString
  )
  begin {
    $FromString = "亞惡壓圍醫爲壹逸隱榮營衞驛謁圓鹽緣艷應歐毆櫻奧橫溫穩假價禍畫會悔海繪壞懷慨槪擴殼覺學嶽樂喝渴褐罐卷陷勸寬漢關歡觀氣祈既歸器僞戲犧舊據擧虛峽挾狹鄕響曉勤謹區驅勳薰徑莖惠揭溪經螢輕繼鷄藝擊缺硏縣儉劍險圈檢獻權顯驗嚴廣效恆黃鑛號國黑穀碎濟齋殺雜參棧蠶慘贊殘絲祉視齒兒辭濕實寫社者煮釋壽收臭從澁獸縱祝肅處暑署緖諸敍將祥稱涉燒證奬條狀乘淨剩疊繩壤孃讓釀觸囑神眞寢愼盡圖粹醉穗隨髓樞數瀨聲齊靜竊攝節專淺戰踐錢潛纖禪祖雙壯爭莊搜插巢曾瘦裝僧層總騷增憎藏贈臟卽屬續墮對體帶滯臺瀧擇澤擔單膽嘆團斷彈遲癡蟲晝鑄著廳徵聽懲敕鎭塚遞鐵點轉傳都燈當黨盜稻鬭德獨讀突屆難貳惱腦霸拜廢賣梅麥發髮拔繁晚蠻卑祕碑濱賓頻敏甁侮福拂佛倂竝塀餠邊變辨瓣辯勉步寶豐襃墨飜每萬滿免麵默彌譯藥與豫餘譽搖樣謠來賴亂覽欄龍隆虜兩獵綠淚壘類禮勵戾靈齡曆歷戀練鍊爐勞郞朗廊樓錄灣"
    $ToString =   "亜悪圧囲医為壱逸隠栄営衛駅謁円塩縁艶応欧殴桜奥横温穏仮価禍画会悔海絵壊懐慨概拡殻覚学岳楽喝渇褐缶巻陥勧寛漢関歓観気祈既帰器偽戯犠旧拠挙虚峡挟狭郷響暁勤謹区駆勲薫径茎恵掲渓経蛍軽継鶏芸撃欠研県倹剣険圏検献権顕験厳広効恒黄鉱号国黒穀砕済斎殺雑参桟蚕惨賛残糸祉視歯児辞湿実写社者煮釈寿収臭従渋獣縦祝粛処暑署緒諸叙将祥称渉焼証奨条状乗浄剰畳縄壌嬢譲醸触嘱神真寝慎尽図粋酔穂随髄枢数瀬声斉静窃摂節専浅戦践銭潜繊禅祖双壮争荘捜挿巣曽痩装僧層総騒増憎蔵贈臓即属続堕対体帯滞台滝択沢担単胆嘆団断弾遅痴虫昼鋳著庁徴聴懲勅鎮塚逓鉄点転伝都灯当党盗稲闘徳独読突届難弐悩脳覇拝廃売梅麦発髪抜繁晩蛮卑秘碑浜賓頻敏瓶侮福払仏併並塀餅辺変弁弁弁勉歩宝豊褒墨翻毎万満免麺黙弥訳薬与予余誉揺様謡来頼乱覧欄竜隆虜両猟緑涙塁類礼励戻霊齢暦歴恋練錬炉労郎朗廊楼録湾"
  }
  process {
    tr -TargetString $TargetString -FromString $FromString -ToString $ToString
  }
}
PS C:\> ConvertFrom-Kokijitentai "慶應義塾大學"
慶応義塾大学
PS C:\> ConvertFrom-Kokijitentai "智辯和歌山"
智弁和歌山
PS C:\> ConvertFrom-Kokijitentai "寶焼酎"
宝焼酎

*1:http://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/pdf/joyokanjihyo_20101130.pdf

*2:「餠」「辨」「瓣」「辯」の4字は上手く抽出できなかったので手作業で追加した

*3:http://satob.hatenablog.com/entry/2019/06/18/013811

ソフトウェア考古学論考 (2)

どっちかもいうとこっちが本題。

さて、ソフトウェア考古学の目的としては、

の他にも、poorly-documentedなレガシーシステムソースコードから、その仕様(あるいは意図)を読み解き復元し、システム再構築のインプットにするというケースが考えられます。

システムの技術的負債の解消や開発者の技術継承などを目的としてシステムを再構築する考え方としては、Fowler(2014)による犠牲的アーキテクチャ*1、および高橋 (2017)による式年遷宮アーキテクチャ*2が提案されています。 システム再構築にあたっては、セカンドシステム症候群を避けるためにも、まずは欲張らない(つまり、現行システムと同程度の)機能をカバーするよう開発を行うわけですが、その際これまで現行システムが本番環境で踏みつけてきた様々なコーナーケースの地雷を避けるには、現行システムの処理がなぜ現在のようになっているのかの理解が欠かせません。

ここで「システム再構築のインプットとなる設計思想の復元」を目的としたソフトウェア考古学的活動が必要になると思うのですが、そのような論文は見当たりません。とすると、

  1. アジャイルソフトウェア開発における知識の保存は成功を収めた。犠牲的アーキテクチャに基づくシステム再構築に耐え得るだけの知識を保存することができた(≒問題にならなかった)
  2. 犠牲的アーキテクチャに基づくシステム再構築は、ソフトウェア考古学的活動を伴っていない。十分な業務知識に基づくトップダウンの再設計だけで、刷新前のシステムと同程度の機能を実装できている(≒やっぱり問題にならなかった)
  3. ソフトウェア考古学的活動による設計思想の復元に成功した例はない。システム再構築は単純なストレートコンバージョンで行われている(≒成功していないので何も書けない)

のどれかなのかなぁと思うのですが、どうなんでしょうね?(3に一票入れたいのですが、悲観的に過ぎるかな?)

*1:Martin Fowler, "Sacrificial Architecture", 2014. URL: http://bliki-ja.github.io/SacrificialArchitecture/

*2:高橋征義, "青空文庫式年遷宮アーキテクチャ: 青空文庫200周年に向けて", 青空文庫20周年記念シンポジウム「青空文庫の今とこれから」, 2017. URL: https://www.slideshare.net/takahashim/aozora20th-2017

ソフトウェア考古学論考 (1)

ソフトウェア考古学(Software Archaeology)という考え方は、OOPSLA 2001で提唱されたのが始まりのようです *1Wikipediaにある通り *2 、poorly-documentedなレガシーシステムソースコードやドキュメントをどう読み解くか?というところから始まったようなのですが、現時点では、Google Scholarで調べる限り、ソフトウェア考古学の研究分野は大きく以下の2つに分かれているように見えます。

  • レガシーシステムの保守開発の効率化。poorly-documentedなレガシーシステムソースコードから、その仕様(あるいは意図)を読み解き、保守開発を確実に実施する(またはリスクを低減する)。たとえばBryon (2009)*3など。
  • システムの仕様理解の補助。昔から開発の続いているソフトウェアのソースコードの変遷やバグの出かたを変更履歴から読み解くことで、現時点のソフトウェア(これ自体はいわゆるレガシーでない場合もある)の仕様理解を助けたり、品質向上の助けにしたりする。たとえば Carrington et al. (2003)*4など。

分析に用いる手法や、その目的から見ると、後者はどちらかといえば「ソフトウェア考古学」というより「ソフトウェア発生学 (Software Embryology)」と呼ぶべきかもしれません。

現代のアジャイルソフトウェア開発においては、知識を個人でなく組織に溜め込むことで、必要なドキュメントを減らそう、それで代替できるドキュメントは作らないようにしよう、という考え方があるようです(聞いただけの話ですが……)。 ただ、ソフトウェア考古学が生まれた経緯を見るに「知識を組織に溜め込むことで、保守に必要な知識が失われないようにする」のは、果たして可能なのだろうか?ドキュメントを作らないことで、逆にソフトウェア考古学の出番が増えやしないか?という点は疑問に思っています。開発チームは存続していても、メンバーの退職・異動・昇進などのイベントを経ることで、どうしても開発開始当初に考えられていた設計上の思想はチーム内でも薄まっていきます。大量のバックグラウンドや経緯や意図を伝えるには、それに見あった量のドキュメントが必要なのでは?

アジャイルソフトウェア開発宣言がまとめられたのは2001年、今年で18年です。アジャイルソフトウェア開発の黎明期に作られたソフトウェアの開発思想や背景は、今でもチーム内に知識として保存されているのでしょうか?また、ソフトウェア考古学なしでもそのソフトウェアの保守開発に立ち向かえるのでしょうか?

*1:Ward Cunningham, Andrew Hunt, Brian Marick, Dave Thomas, "Software Archeology: Understanding Large Systems", OOPSLA 2001 Workshop, URL: https://web.archive.org/web/20100612232147/http://www.visibleworkings.com/archeology/position-papers.html

*2:https://en.wikipedia.org/wiki/Software_archaeology#cite_note-RGBH-1

*3:Bryon Moyer, "Software Archeology: Modernizing Old Systems," Embedded Technology Journal, March 4, 2009. URL: https://www.omg.org/adm/docs/Software_Archeology_4-Mar-2009.pdf

*4:Carrington, David, and S-K. Kim. "Teaching software design with open source software." 33rd Annual Frontiers in Education, 2003. FIE 2003.. Vol. 3. IEEE, 2003. URL: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.103.2003&rep=rep1&type=pdf

How to separate a string into codepoint-wise characters with PowerShell

Context:

You have a Unicode string that contain non-ASCII characters as well as ASCII characters. You want to separate that string into characters.

Problem:

If you split the string with the code below:

$TemporaryArray = $InputString -split "";
$ResultArray = $TemporaryArray[1..($TemporaryArray.length-2)];

You will have a problem: characters that represented as surrogate pair (U+10000 ~ U+10FFFF) will separated high surrogate and low surrogate (they are not character).

Reason:

PowerShell -split operator is not surrogate pair aware, and it seems by design.

Solution:

Once convert the string into UTF32 byte-array, and separate it into codepoints (4-byte length), and convert them to String object.

$ResultArray = @();
$InputStringBytes = [Text.Encoding]::UTF32.GetBytes($InputString);
for ($i=0; $i -lt $InputStringBytes.length; $i+=4) {
     $ResultArray += [Text.Encoding]::UTF32.GetString($InputStringBytes, $i, 4);
}

Limitation:

This method separate a string into each codepoint, so the Unicode ligatures (it consists of two or more codepoints) are illegally separated into codepoints. You can use icu.net for this purpose.

Difference of behavior of String#split() in Java and -split operator in PowerShell

Both of String#split() in Java and -split operator in PowerShell take regex as argument, and split string into a list or an array, but there is some difference in behavior when you pass an empty string as argument.

In Java:

System.out.println("abc".split("").length); // -> 3

Whereas in PowerShell:

PS > ("abc" -split "").Length  # -> 5

Because ("abc" -split "") makes @("","a","b","c","").

Also in PowerShell:

PS > ("abc".split("")).Length  # -> 1

Because split("") will not split the target string at all.