全銀システム障害 まとめ 2023/10/11 深夜時点

目的

2023-10-10より11銀行(三菱 UFJ
銀行、りそな銀行、埼玉りそな銀行、関西みらい銀行、山口銀行、北九州銀行、三菱 UFJ
信託銀行、日本カストディ銀行、JP モルガン・チェース銀行、もみじ銀行、商工組合中央
金庫10)において、他行宛の振込取引ができない事象が発生した。

この資料は2023/10/11段階での情報をまとめたものとする

障害発生箇所


次期全銀システム基本方針より

十月の三連休中に全銀システムと各銀行を結ぶ中継コンピューター(RC)についてRC17からRC23への切り替えを行った。
この切り替えはソフトウェアの更新だけでなく、ハードウェア、並びに接続先の銀行(14行)について対応が必要であった。

その後、14行中、11行でRC中の銀行間手数料を処理するプログラムで問題が発生して他行宛の振込取引ができない状況となった。

なお、問題の発生しない3行については銀行館手数料の計算を自行のシステムで行い銀交換手数料を0で送信していた。

原因

現時点で障害の発生した箇所はわかっているが、なぜ問題が発生したかを究明できていない。
なお、銀行間手数料についてはRC17の段階で存在した処理である。

銀行館手数料のプログラムについては、どのようなプログラミング言語で構築については不明。(後ほど回答があるはず COBOLかC)

暫定対策

10/10の時点で問題の発生箇所はわかっており、プログラム修正を行ったが不具合の回収をしたが、テストで失敗したため適用を見送った

10/11では銀行間手数料を0として送信し、付替電文で銀行間手数料を対応する

なぜ切り戻さないか?

全銀だけの問題でなく、RC23にバージョンアップした14行でも対応が必要であり、切り戻しのリスクが高いと判断してプログラム修正をしている。

冗長化が機能していない?

同じソフトウェアが入っているから二系あっても機能しない。

片方RC17のままにしたらどうかという質問が、記者からあったが、バージョン違いを混在させるのは構成が複雑になりすぎてリスクが余計高くなるから、その選択肢はかなり厳しいと思われる。

著者の疑問

  • 原因がわからないということは類似不具合が別の箇所に存在している可能性がある。この点についてどう考えるか?
  • 別の電文で対応するということは、想定より多くの電文を投げると理解する。この場合の通信量増加によるリスクは存在しないのか?

参考

全国銀行データ通信システムの不具合について(その4)
https://www.zengin-net.jp/announcement/pdf/announcement_20231011_2.pdf

【全銀ネット有識者会議】事務局説明資料
https://www.zengin-net.jp/zengin_net/pdf/230116_paper2.pdf)

次期全銀システム基本方針
https://www.zengin-net.jp/announcement/pdf/20230316_basicpolicy_8Z.pdf

全銀ネットが簡素化したパッチを適用へ、2日で500万件超の送金に影響

https://xtech.nikkei.com/atcl/nxt/news/18/16084/

【ノーカット】発生から30時間超 「全銀ネット」がシステム障害受け会見 復旧のめどは? 三菱UFJ銀行・りそな銀行などで他銀行あての振り込み取引に遅延(2023/10/11)ANN/テレ朝

https://www.youtube.com/watch?v=Fm3RwYv8XiM

ごん狐考察

背景

本稿では考察を間違えると「社会常識や人間的な感情への想像力がすっぽり抜け落ちている」と罵倒される、ごん狐について考察を行う。

今回は以下のサイトのごん狐をもとに考察するのでネタバレを懸念する方は一度、読み直してから、考察を見ていただきたい。
https://www.aozora.gr.jp/cards/000121/files/628_14895.html

時代と場所について

ごん狐の物語は、あるいは時代の経過がされた物語については、その物語の時代背景と、地域の特性を考える必要がある。

キリスト圏で、日本の多神教の考えをもとに物語を読んだら読み違えるし、現在の価値観で忠臣蔵を読んだらメンヘラの被害に遭った老人虐待の物語である。

 むかしは、私たちの村のちかくの、中山なかやまというところに小さなお城があって、中山さまというおとのさまが、おられたそうです。

まず、この文章により、中山という地域であることが確定する。
作者の新美南吉氏が愛知県半田市の出身であることより、その付近の地域であると予測がつく。
結果、尾張 中山城の付近の物語であろう。
https://www.hb.pei.jp/shiro/owari/nakayama-jyo/

次に時代背景であるが、これは、火縄銃の普及〜廃藩置県の前の時代であることが確定する。

何を煮たか問題

ごん狐では物語の序盤に大きな鍋で何かをぐずぐず煮ていた。

 こんなことを考えながらやって来ますと、いつの間まにか、表に赤い井戸のある、兵十の家の前へ来ました。その小さな、こわれかけた家の中には、大勢おおぜいの人があつまっていました。よそいきの着物を着て、腰に手拭てぬぐいをさげたりした女たちが、表のかまどで火をたいています。大きな鍋なべの中では、何かぐずぐず煮えていました。
「ああ、葬式だ」と、ごんは思いました。

この回答を誤ると「社会常識や人間的な感情への想像力がすっぽり抜け落ちている」とレッテルを貼られるので注意深く考える必要がある。

説としては以下の2種類が存在する。

  • 何らかの料理
  • 兵十のおっかあ

何らかの料理を煮てた説

現在の日本人の価値観においては葬儀に振る舞う何らかの料理を煮ていたと判断されうるものであるが、「何を煮ていたか」の問いに何らかの料理というのは解答にはなっていないと考えられる。

ここでは具体的に何を煮ていたかを考察する必要がある。

ごん狐が「ああ、葬式だ」と判断した基準の一つに、大きな鍋で何かを煮ていた描写があることより、これは葬儀に関係する料理の可能性が高い。

愛知県においては葬儀で「涙汁」と呼ばれるものを飲む風習があることより、「涙汁」が一つの可能性となる。
https://www.famille-kazokusou.com/guide/post-15.html

幸い、作中の描写にも唐辛子が存在していることは確認できる。

百姓家ひゃくしょうやの裏手につるしてあるとんがらしをむしりとって、いったり、いろんなことをしました。

唐辛子を煮込んでいているのであれば、その刺激臭で、葬儀が行われていたと判断することは妥当であろう。

ただし、この涙汁が「大きな鍋なべの中では、何かぐずぐず煮えていました。」と言われた表現で作成できるものかどうかは尾張の人間でない私には判断がつかない。

兵十のおっかあを煮てた説

現在において、遺体を煮るということは考えずらいが、狐が喋る世界線において遺体を煮るという行為がそれほど間違った行為であるかは議論の余地がある。
実際、カチカチ山という狸が喋る世界戦では、人間を煮ていた事例が存在する。

ではなぜ、遺体を煮る必要があったかを考える必要がある。

  • 食べるため
  • 遺体の処置のため

食べるため

遺体を食べるという行為について、いくつかの合理的な理由が考えられる.

  • 飢饉で食べるものがなかったため
  • 宗教的儀式のため
飢饉で食べるものがなかったため

半田町史によると、何度か飢饉が発生していることが確認できる。以下の369コマ目を参照。
https://dl.ndl.go.jp/info:ndljp/pid/1020258

しかしながら、作中で飢饉で食べるものがなかったとは考えづらい。
作中にイワシや栗などの食べ物が描写されており、村人が飢えていた可能性は低いと考えられる。

宗教的儀式のため

死者への愛着から魂を受け継ぐという儀式的意味合いは否定できない。
事実、日本においても「骨噛み」の儀式の存在する地域はあり、物語の舞台である愛知県にもその風習はあると言われている。
https://www.sougiya.biz/kiji_detail.php?cid=1521

遺体の処置のため

遺体をお湯で洗い清める湯灌という処置は知られているが、前述の「ぐずぐず煮えていました」という表現には当てはまらないと考えられる。

では何人かの犯罪者がやったように遺体を煮て処理をしたとか、骨格を取り出すために煮たという可能性は否定できない。
前述の半田町史を読んでみたが、葬儀の文化については記載がなかったため、ここでは結論が出ない。

何を煮たか問題まとめ

いずれの説も確証が存在せず、少なくとも現在の価値観においては「何らかの料理を煮てた説」ことになるのであろうが、この場合、具体的に何を煮ていたかという問いに答えていないことになり、答えのない問題をどう考えるかという思考のプロセスや意見の調整を行うためには優れた問いであるが、この問いを間違ったからといって「社会常識や人間的な感情への想像力がすっぽり抜け落ちている」されると問いとして適切であるかについては大きな疑義が残る。

なぜ狐が喋っているのか問題

ごん狐は狐でありながら、喋っている描写があるが、これが何故なのかを考える必要がある。

  • 狐語で喋っているのであり、人間と意志疎通はできない説
  • 物の怪の類である説
  • 実は狐ではなく人間だった説

狐語で喋っているのであり、人間と意志疎通はできない説

実は狐語で喋っているだけで、人間には通じていない説がある。
確かに、人間とコミュニケーションをしている描写はないので、この可能性はある。
しかし、以下のようにごん狐は獣でありながら火を操ることができる。

はたけへ入って芋をほりちらしたり、菜種なたねがらの、ほしてあるのへ火をつけたり、百姓家ひゃくしょうやの裏手につるしてあるとんがらしをむしりとって、いったり、いろんなことをしました。

つまり、通常の動物を超えた能力を有している可能性があると考えられる。

物の怪の類である説

物の怪の類であるなら人語を喋るし、火も操ることが可能であると考えられる。
幸い、日本には狐火と呼ばれるものが存在するので狐の妖怪であれば使えても不思議ではないだろう。
ただし、それだけの妖怪が火縄銃で殺傷できるかについて疑問が残る

実は狐ではなく人間だった説

実はごん狐と言っているが、人間だったのではなかろうか?
森に住んでいる浮浪者だったが、火縄銃で人間を射殺したとなると問題になるので、村ぐるみで隠蔽を行ったのではなかろうか?
狐でなく人間であれば、言葉を喋れるし、火も使える、そして火縄銃で殺せる。

まとめ

今回は、ごん狐について考察を行ったが、「社会常識や人間的な感情への想像力がすっぽり抜け落ちている」ので何を煮ているか問題については、明確な答えを出すことができなかった。
ぜひ、社会常識や人間的な感情への想像力が豊富な知識人の皆様には納得のできる明確な回答を教えていただきたいところである。

Googleのバグ予測アルゴリズムをnode.jsで実装してみた件

はじめに

Googleのバグ予測アルゴリズムというものがあります。
https://www.publickey1.jp/blog/11/post_193.html

簡単にいうと最近、数多くバグ修正したコードはバグが発生しやすいという考えになっています。

今回はnode.jsでそれを実装してみました。

事前準備

simple-gitをインストールします。

npm install --save simple-git

コード

以下のロジックではコミットログのメッセージにfixという文字が会ったらバグ修正をしたとみなしてます。

const simpleGit = require('simple-git')
const fs = require('fs')

function moveFile(path) {
  moves = path.match(/(?<=\{)[\s\S]*?(?=\})/g)
  if (!moves) return null
  let before = path
  let after = path
  moves.map((move)=>{
    const names = move.split(' => ')
    before = before.replace(move, names[0])
    after = after.replace(move, names[1])
  })
  return {
    before: before.replace(/{|}/g, '').replace(/\/\//g, '/'),
    after: after.replace(/{|}/g, '').replace(/\/\//g, '/')
  }
}
function checkMessage(message) {
  // TODO プロジェクトのルールに合わせてコミットログのメッセージを調整する
  if (message.indexOf('fix') >=0) return true
  return false
}

async function getCommitLog(repUrl, branch, workFolder) {
  const result = {}
  try {
    if (fs.existsSync(workFolder)) {
      fs.rmdirSync(workFolder, { recursive: true })
    }
    fs.mkdirSync(workFolder)
    await simpleGit().clone(repUrl, workFolder,  ['-b', branch])  
    const git = simpleGit(workFolder)
    await git.log({'--stat': null, '--stat-width': 1024}, (err, logs) => {
      //console.log(err, logs)
      logs.all.reverse().map((log)=>{
        //console.log(log.hash, log.date, log.message, log.author_name)
        if (!log.diff) return

        // コミットログのチェックして集計対象か調べる
        const fixedLog = checkMessage(log.message)

        log.diff.files.map((file)=>{
          //console.log('  ', file)
          let fileName = file.file
          if (fileName.indexOf('=>') > 0) {
            const moveResult = moveFile(fileName)
            if (moveResult) {
              fileName = moveResult.after
              if (result[moveResult.before]) {
                result[moveResult.after] = result[moveResult.before]
                delete result[moveResult.before]
              }
            } else {
              console.warn('unexpected filepath' , log.hash, fileName)
              return
            }
          }
          if (!result[fileName]) {
            result[fileName] = {
              file: fileName,
              totalCount: 0,
              createdDate: log.date,
              fixCount: 0,
              fixHistory: []
            }
          }
          ++result[fileName].totalCount
          if (result[fileName].createdDate > log.date) {
            result[fileName].createdDate = log.date
          }
          if (fixedLog) {
            ++result[fileName].fixCount
            result[fileName].fixHistory.push(log.date)
          }

        })
      })
    })
    return result
  }
  catch (e) { 
    console.log(e)
  }
}

function collect(all) {
  const files = []
  const result = []
  for (const key in all) {
    if (all[key].fixCount > 0)  {
      files.push(all[key])
    }
  }
  const baseDate = new Date()
  files.map((file)=>{
    //console.log(file.file)
    const createdDate = new Date(file.createdDate)
    const timeCreateToNow = baseDate-createdDate
    let score = 0
    file.fixHistory.map((history)=>{
      const t = ((new Date(history))-createdDate) / timeCreateToNow
      //console.log(file.createdDate, history, t)
      score = score + (1/(1+Math.exp(-12*t+12)))
    })
    result.push([file.file, score])
  })
  result.sort((a,b)=>b[1]-a[1])
  return result
}

async function main() {
  const all = await getCommitLog('https://github.com/vuejs/vue.git', 'master', './work')
  const result = collect(all)
  console.log(result)
}

main()

プロセスのダンプファイルを取得してデバッグする方法

プロセスのダンプファイルを取得してデバッグする方法

たとえばハングしているプロセスのダンプファイルを取得して別の環境で解析する方法について記述する。

Windowsの場合

ダンプファイルの取得方法

タスクマネージャーからプロセスを選択してダンプファイルを作成する。

しばらくすると、一時フォルダにDMPファイルが作成される。

ダンプファイルをVisualStudioでデバッグする方法

VisualStudioを使用することでDMPファイルを開いてデバッグを行える。
.NETFrameworkを使用している場合

.NETFrameworkを使用していない場合

.NETFrameworkを使用している場合は「マネージドのみでデバッグ」または「混合でデバッグ」を選択する。
.NETFrameworkを使用していない場合は「ネイティブのみでデバッグ」を選択する。

「スレッド」ウィンドウにはプロセスのスレッドの一覧が表示される。
「呼び出し履歴」ウィンドウには選択したスレッドの呼び出し履歴が表示される。
呼び出し履歴を選択することで、その箇所のコードが表示される。
これらを利用することでデッドロックしている箇所をしらべることができる。

GDBが使える環境の場合

ダンプファイルの取得方法

gdbを起動して以下のコマンドを実行する。

attach プロセスID
gcore 出力ファイル名
detach
quit

ダンプファイルをgdbでデバッグする方法

起動方法

gdb 実行ファイル名 ダンプファイル

スレッドの一覧を表示する方法
Debugging programs with multiple threads

(gdb) info threads
  Id   Target Id         Frame
  6    Thread 0x7f24db490740 (LWP 11479) 0x00007f24da859f47 in pthread_join ()
   from /lib64/libpthread.so.0
  5    Thread 0x7f24d847f700 (LWP 11484) 0x00007f24da548e2d in nanosleep ()
   from /lib64/libc.so.6
  4    Thread 0x7f24d8c80700 (LWP 11483) 0x00007f24da548e2d in nanosleep ()
   from /lib64/libc.so.6
  3    Thread 0x7f24d9481700 (LWP 11482) 0x00007f24da548e2d in nanosleep ()
   from /lib64/libc.so.6
  2    Thread 0x7f24d9c82700 (LWP 11481) 0x00007f24da548e2d in nanosleep ()
   from /lib64/libc.so.6
* 1    Thread 0x7f24da483700 (LWP 11480) 0x00007f24da548e2d in nanosleep ()
   from /lib64/libc.so.6

スレッドの切り替え方法

(gdb) thread 5
[Switching to thread 5 (Thread 0x7f24d847f700 (LWP 11484))]

呼び出し履歴の表示
backtraceコマンドを使用すると現在のスレッドの呼び出し履歴が確認できる。

(gdb) bt
#0  0x00007f24da548e2d in nanosleep () from /lib64/libc.so.6
#1  0x00007f24da579704 in usleep () from /lib64/libc.so.6
#2  0x00000000004012e6 in f(int) ()
#3  0x00000000004029f0 in void std::_Bind_simple<void (*(int))(int)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#4  0x00000000004028fd in std::_Bind_simple<void (*(int))(int)>::operator()()
    ()
#5  0x0000000000402896 in std::thread::_Impl<std::_Bind_simple<void (*(int))(int)> >::_M_run() ()
#6  0x00007f24db03a070 in ?? () from /lib64/libstdc++.so.6
#7  0x00007f24da858dd5 in start_thread () from /lib64/libpthread.so.0
#8  0x00007f24da581ead in clone () from /lib64/libc.so.6

Javaの場合

ヒープダンプを作成する

jmapコマンドでヒープダンプを作成することが可能。

jmap -dump:format=b,file=<file-path> <pid> 

作成したヒープダンプからメモリの使用状況を調べることができる。
ヒープダンプを解析するにはjvisualvmなどを利用する。

スレッドダンプを作成する

jstackコマンドを使用するとスレッドダンプが作成することが可能。

jstack -l <pid> 

現時点のスレッドの情報を取得することができ、デッドロックをしていると以下のようになる。


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000003aa8138 (object 0x00000007018084d8, a ChopSticks),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000003aa8088 (object 0x0000000701800bf0, a ChopSticks),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at Human.eat(Human.java:35)
        - waiting to lock <0x00000007018084d8> (a ChopSticks)
        - locked <0x0000000701800bf0> (a ChopSticks)
        at Human.run(Human.java:18)
"Thread-0":
        at Human.eat(Human.java:35)
        - waiting to lock <0x0000000701800bf0> (a ChopSticks)
        - locked <0x00000007018084d8> (a ChopSticks)
        at Human.run(Human.java:18)

Found 1 deadlock.

コアダンプからヒープダンプとスレッドダンプを作成する

タスクマネージャーで作成したjavaのコアダンプはjvisualvmで解析することができる。
jvisualvmを起動してVMのコアダンプからjavaのダンプファイルを開いてヒープダンプやスレッドダンプを作成可能になっている。

jvisualvmで起動中のプロセスからヒープダンプやスレッドダンプを作成する

jvisualvmは起動中のプロセスから直接スレッドダンプやヒープダンプを作成することができる。

Visual Studio 2019でリモートデバッグを行う

Visual Studio 2019でリモートデバッグを行う

開発環境の入っていない端末でデバッグを行う場合、リモートデバッグを使用することができる。

以下参照
https://docs.microsoft.com/ja-jp/visualstudio/debugger/remote-debugging?view=vs-2019

クライアントのセットアップ

クライアントにリモート ツールをインストールする。

クライアントでRemote Debuggerを起動する

「Configure remote debugging」ボタンを押下することで、外部PCからのデバッグを受け付ける準備が整う。

VisaulStudioでデバッグする方法

Visual Studioからクライアントのプロセスにアタッチしてデバッグする方法

Visual Studioの「デバッグ」メニューからを選択して「プロセスにアタッチ」を選択する。

接続先の検索コンボボックスで接続先のPCを選択するか、検索ボタンを押下する。

デバッグをしたいプロセスが動作しているクライアントを選択する。

クライアントのプロセス一覧が表示されるのでデバッグ対象のプロセスを選択する。

C#のプロジェクトから起動してデバッグする方法

(1) Visual Studioのプロジェクトで作成したDebugビルドのEXEをクライアントにコピーする。この際、パスはクライアント側とVisualStudio側で合わせること

(2)プロジェクトのプロパティを開いて、開始オプションを変更する。

・「リモートコンピュータを使用する」にチェックを入れる。
・コンピュータ名とポート番号を入力する。
「コンピュータ名:番号」

(3)VisualStudioからデバッグを開始するとクライアント端末でデバッグ対象のプロセスが起動する。

https://docs.microsoft.com/ja-jp/visualstudio/debugger/remote-debugging?view=vs-2019

C++のプロジェクトから起動してデバッグする方法

(1)プロジェクトのプロパティを開いて、デバッグの設定を行う。

項目 設定値
起動するデバッガー リモート Windowsデバッガー
リモートコマンド クライアントで実行するEXEのパス
作業ディレクトリ プロセス起動時の作業フォルダ
リモートサーバー名 クライアントのPC名
配置ディレクトリ クライアントでEXEファイルを配置するフォルダ

(2)構成マネージャーで配置にチェックを付ける

(3)デバッグを開始すると、クライアント上の配置フォルダにEXEを配置してプロセスを開始してデバッグが可能になる。
なお、C++のプロセスを実行する場合、クライアントに再配布用のDLLをインストールだけでは動作しないので、VisualStudioのあるマシンからデバッグ用のDLLをコピーしておく必要がある。単純なMFCを動かす場合は以下のDLLが必要である。

  • mfc140ud.dll
  • ucrtbased.dll
  • vcruntime140_1d.dll
  • vcruntime140d.dll

https://docs.microsoft.com/ja-jp/visualstudio/debugger/remote-debugging-csharp?view=vs-2019

デスマーチの概念を初めて提唱した人の取り扱い

はじめに

デスマーチについてのWikipediaの記述が凄く引っかかり、また、多くの人がそれを無批判に受け入れているので記事を書きます。

2020/06/07時点のWikipediaより

https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%89%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%BB%E3%82%B1%E3%83%BC%E3%83%8B%E3%83%83%E3%83%92

アンドリュー・ケーニッヒ (プログラマー)(英語版) - アメリカ合衆国のプログラマー。デスマーチの概念を初めて提唱した。

https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%81

デスマーチ (death march) とは、プロジェクトにおいて過酷な労働状況をいう。本来は、コンピュータプログラマのアンドリュー・ケーニッヒ(英語版)によって1995年に示された、コンピュータシステムのアンチパターンのうち、プロジェクトマネジメント上の問題点の1つとして示した言葉である。

アンドリュー・ケーニッヒとアンチパターン

アンドリュー・ケーニッヒは英語版のWikiPediaによると「アンチパターン」で知られるとあります。
https://en.wikipedia.org/wiki/Andrew_Koenig_(programmer)

またこのページによると以下の経緯があるようです。

1995年 アンドリュー・ケーニッヒは、最初にJournal of Object Oriented Programの1995年3月-4月号でアンチパターンという用語を造語しました。"アンチパターンはパターンのようなものであり、解決策の代わりにそれが解決策のように表面的に見える何かを与えるが、1つではないことを除いて、ちょうどである"。

1998 Linda Risingは、アンドリュー・ケーニッヒのアンチパターンの定義をThe patterns handbook: techniques, strategies, and applicationsの中で再掲しています。

1998年 書籍「 AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis」という本は、この用語を広めました。

AntiPatterns: Refactoring Software, Architectures, and Projects in Crisisは日本では以下の書籍として出版されています。

アンチパターン―ソフトウェア危篤患者の救出
https://www.amazon.co.jp/dp/4797307587

英語版 Anti Pattern のWikiPediaの記載

2020/06/07時点のWikipedia
https://en.wikipedia.org/wiki/Anti-pattern

An anti-pattern is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive. The term, coined in 1995 by Andrew Koenig, was inspired by a book, Design Patterns, which highlights a number of design patterns in software development that its authors considered to be highly reliable and effective.

The term was popularized three years later by the book AntiPatterns, which extended its use beyond the field of software design to refer informally to any commonly reinvented but bad solution to a problem. Examples include analysis paralysis, cargo cult programming, death march, groupthink and vendor lock-in.

1995年にアンドリュー・ケーニッヒが「anti-pattern」という言葉を造語し、3年後に書籍によって有名になったという記述は同じです。また、書籍化を行う際に、ソフトウェア設計を超えた分野についての問題を取り扱うようになり、そのなかにデスマーチも入っていると読み取れます。

アンチパターン―ソフトウェア危篤患者の救出におけるデスマーチの話

p258,259でデスマーチの話題をとりあつかっていますが、そこにアンドリュー・ケーニッヒは登場しません。
デスマーチの定義についてはエドワードヨードンの定義を参照しています。

エドワードヨードンのデスマーチの定義

エドワードヨードンは書籍デスマーチにおいて自身の考えるデスマーチの定義を行っています。

https://www.amazon.co.jp/dp/4810189821
https://www.amazon.co.jp/dp/4822282716

なお、第一版、第二版ともにアンチパターンについても、アンドリュー・ケーニッヒにも言及はありません。

まとめ

・アンドリュー・ケーニッヒはアンチパターンという言葉を提唱した。
・アンチパターンにデスマーチなどのソフトウェアの設計の範囲のものが追加されたのは書籍出版のタイミングと思われる。
・書籍のデスマーチの項目にはエドワードヨードンのデスマーチの定義が参照されている。

つまり、今、日本語Wikipediaで言われている「アンドリュー・ケーニッヒ がデスマーチの概念を初めて提唱した。」という記述には疑義がある。

これを確認するにはJournal of Object Oriented Programの1995年3月-4月号を読む必要がある。

PowerShellでVisualStudioを操作する

PowerShellでVisualStudioを操作する

VisualStudioをスクリプトで操作したい場合がたまにあります。たとえば、ソースコード中の関数の一覧を列挙したり、コードを自動生成する場合などです。

現VisualStudioのマクロの制限

以下の記事で紹介している復活したVisualStudioのマクロはJScriptからVisualStudio.DTEを利用して、VisualStudioを操作しています。

復活のVisualStudioのマクロ
https://qiita.com/mima_ita/items/c56d6e2657cc836af7ac

じつは旧マクロで実行できたことが、新しいマクロでは実行できないケースがあります。たとえばCodeElementで取得した関数のパラメータの列挙はかってのVisualStudioのマクロでは簡単におこなえましたが、新VisualStudioのマクロでは実行できません。

これはJScriptではインターフェイスの明示がおこなえないためと考えられます。

C#からのVisualStudioの操作

C#からのVisualStudioの操作は容易に行えます。
これを行うには参照の追加からアセンブリ→拡張をえらびEnvDTEXXXを追加する必要があります。

EnvDTEのバージョンはわかりにくいですが、EnvDTE80はEnvDTEに足りない機能を拡張したもの、EnvDTE90はEvnDTE+EnvDTE80に足りないものを拡張したものとなっています。つまり、EnvDTEを参照したあとに必要に応じて拡張されたものを参照することになります。

以下のコードは起動しているVisualStudio2019のソリューションから関数の一覧を表示するサンプルです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace VSTest
{
    class Program
    {
        static void EnumCodeElements(EnvDTE.CodeElements elems)
        {
            foreach (EnvDTE.CodeElement elem in elems)
            {
                if (elem.Kind == EnvDTE.vsCMElement.vsCMElementFunction)
                {
                    EnvDTE.CodeFunction func = elem as EnvDTE.CodeFunction;
                    Console.WriteLine("{0} {1}", func.Type.AsFullName, func.FullName);
                    foreach(EnvDTE.CodeParameter param in func.Parameters)
                    {
                        Console.WriteLine("  {0} {1}", param.Type.AsFullName, param.FullName);
                    }
                }
                EnumCodeElements(elem.Children);
            }

        }

        static void EnumProjectItems(EnvDTE.ProjectItems items)
        {
            foreach (EnvDTE.ProjectItem item in items)
            {
                Console.WriteLine(item.Name);
                EnumProjectItems(item.ProjectItems);
                if (item.FileCodeModel != null)
                {
                    EnumCodeElements(item.FileCodeModel.CodeElements);
                }
            }

        }

        static void Main(string[] args)
        {
            EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.16.0");
            foreach (EnvDTE.Project prj in dte.Solution.Projects)
            {
                Console.WriteLine(prj.Name);
                EnumProjectItems(prj.ProjectItems);
            }

        }

    }
}

注意すべき点として、dynamicなどを使用してインターフェイスを明示しない場合、正常に動作しない箇所があるということに注意してください。

PowerShellからのVisualStudioの操作

さてPowerShellから同様の操作した場合、インターフェイスを明示するところで悩むことになります。
これはCOM経由でDTEを操作しているため、PowerShell上ではSystem.__ComObject型として扱われるため、適切なDTEのインターフェイスにキャストできないためです。

ではどうしたらPowerShellからVisualStudioを操作できるのでしょうか。

パッケージマネージャーコンソールでの操作

実はVisualStudioについているパッケージマネージャーコンソールからは簡単にVisualStudioの自動操作が行えます。
これはNugetがPowershellを使用してプロジェクト情報の更新を行っているためと考えられます。

パッケージマネージャーコンソールでは変数$dteにVisualStudioを操作するためのVisualStudio.DTE.16.0がすでに読み込まれており、また、Get-Interfaceコマンドレットを使用してインターフェイスを明示することが可能になっています。

Get-Interfaceの使用例

PM>  Get-Interface $dte.ActiveDocument.ProjectItem.FileCodeModel ([ENVDTE80.FileCodeModel2])

DTE          : EnvDTE.DTEClass
Parent       : System.__ComObject
Language     : {B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}
CodeElements : {System.__ComObject, System.__ComObject, System.__ComObject, System.__ComObject...}
ParseStatus  : vsCMParseStatusComplete
IsBatchOpen  : False

Can't access FileCodeModel from Powershell console in Visual Studio 2017 DTE
https://stackoverflow.com/questions/45237281/cant-access-filecodemodel-from-powershell-console-in-visual-studio-2017-dte

このGet-Interfaceはパッケージマネージャーコンソール内だけで定義されているコマンドであり、その内容は以下の通りです。

PM> (Get-Command Get-Interface).Definition 

    Param(
        $Object,
        [type]$InterfaceType
    )

    [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($Object, $InterfaceType)

NuGetConsoleはNuGetConsole.Host.PowerShell.dll内で定義されているため、これを利用することにより通常のPowerShellからもDTEのインターフェイスを明示することが可能になります。

PowerShellからVisualStudioを操作する例

では、通常のPowerShellからVisualStudioを操作する方法を考えてみます。

下記の例ではNuGetConsole.Host.PowerShell.dllを読み込んだあとDTEを利用してVisualStudioのプロジェクトから関数を取得しています。また、必要におうじて、GetInterfaceを用いてインターフェイスを明示しています。

function EnumCodeElements($elems, $level) {
    $space = " " * $level
    foreach($elem in $elems) {
        If ($elem.Kind -eq 2) { # vsCMElement.vsCMElementFunction
            # Access:
            # https://docs.microsoft.com/en-us/dotnet/api/envdte.vscmaccess?view=visualstudiosdk-2017
            Write-Host " " $space  $elem.Access $elem.Type.AsFullName   $elem.FullName
            foreach($param in $elem.Parameters) {
                Write-Host "    " $space  $param.Type.AsFullName $param.Name
            }
        }
        If ($elem.Children) {
            EnumCodeElements $elem.Children ($level+1)
        }
    }
}

function EnumProjectItem($items, $level) {
    foreach($item in $items) {
        $item = [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($item, [ENVDTE.ProjectItem]) 
        $space = " " * $level
        Write-Host $space $item.Name
        if ($item.FileCodeModel) {
            $fileCodeModel = [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($item.FileCodeModel, [ENVDTE.FileCodeModel])
            $fileCodeModel.Language
            EnumCodeElements $fileCodeModel.CodeElements $level
        }
        If ($item.ProjectItems) {
            EnumProjectItem $item.ProjectItems  ($level+1)
        }
    }
}

[Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGetConsole.Host.PowerShell.dll") > $null

If (-not $dte) {
    $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.16.0")
}
foreach($project in $dte.Solution.Projects) {
  Write-Host "Project:" $project.Name
  EnumProjectItem $project.ProjectItems  0
}

まとめ

今回はVisualStudioをPowerShellで操作する方法を検証しました。
これを利用することでクラスの情報を取得したり、クラスを自動作成したりすることができると考えられます。
ただし、VisualStudioのDTEでFileCodeModelとして認識できるファイルはC#,VB.NET,C++とかぎられており、JavaScriptなどは認識できません。

CodeModelLanguageConstants Class
https://docs.microsoft.com/en-us/dotnet/api/envdte.codemodellanguageconstants?view=visualstudiosdk-2017

OGPってなんだってばよ

OGPってなんだってばよ

OGPがなんだかわからなかったので調べてみました。OGPとははOpen Graph protocolの略で、以下に仕様が公開されています。
https://ogp.me/

OGPを使用することで、Webページをソーシャルグラフのオブジェクトにすることができます。

たとえば、Twitterなどで、URLをつけてツイートすると以下のようにカードが表示されるURLがあります。

これは、そのURLのWebページにOGPのメタデータが設定されており、Twitterはそのメタデータを読み取りカードを作成しています。

また実際にTwitterを投稿せずとも下記のサイトにURLを張り付けることでどのようなカードが作成されるかを確認することが可能です。
https://cards-dev.twitter.com/validator

OGPのメタデータの例

たとえば、先の朝日新聞のURLは以下のようなOGPのメタデータを利用しています。

<html lang="ja">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">

略

<meta property="og:locale" content="ja_JP">
<meta property="og:title" content="山形)猫駅長しょこら、JRがお墨付き 制帽プレゼント:朝日新聞デジタル">
<meta property="og:url" content="https://www.asahi.com/articles/ASMDM3Q6FMDMUZHB003.html">
<!--引用可能写真=4-->
<meta property="og:image" content="https://www.asahicom.jp/articles/images/c_AS20191219003081_comm.jpg">
<meta property="og:image:height" content="314">
<meta property="og:image:width" content="600">
<meta property="og:site_name" content="朝日新聞デジタル">
<meta property="og:type" content="article">
<meta property="og:description" content=" 山形県川西町のJR米坂線・羽前小松駅のネコ駅長「しょこら」に19日、JR東日本が観光活性化の期待を込めて、市販されている駅長の制帽のミニチュアを贈った。駅を運営しているNPO法人は今後、イベントなど…">
<meta property="article:author" content="https://www.facebook.com/asahicom/">
<meta property="article:publisher" content="https://www.facebook.com/asahicom/"

この中で必須のメタデータ以下の通りです。

メタデータ 説明
og:title グラフ内に表示すべきオブジェクトのタイトル
og:type オブジェクトのタイプ。設定できる値はObject Typesのに記載されている。※朝日新聞の場合はarticleを指定している
og:url グラフの永続的なIDとして用いられるオブジェクトの基準URL
og:image グラフ内にオブジェクトを表示するときの画像のURL。og:image:width,og:image:heighなどのオプションを付けることも可能

自分でHTMLを作成する場合は、上記の4つを設定します。
og:typeはarticleかwebsiteがよく使われていそうな感じでした。

WordPressで使用する方法

以下のようなプラグインを導入すれば埋め込んでくれます。

Open Graph Protocol Framework

https://wordpress.org/plugins/open-graph-protocol-framework/

シンプルなプラグインです。
プラグインをインストールして有効化するだけで、基本的なogpのメタデータがWordPressの記事に埋め込まれます。
og:image に使用される画像はアイキャッチ画像になります。

All in one SEO Pack

https://wordpress.org/plugins/all-in-one-seo-pack/

WordPressのSEO対策をするためのツールが様々はいっているプラグインです。
機能管理から「ソーシャルメディア」を有効にすることでOGPの設定が行えるようになります。
og:image に使用する画像を細かくカスタマイズすることができます。

トスツイを検証してみる

概要

トスツイとは「@tos」をつけてツイートした場合に、フォロワーのタイムラインに表示されず、ツイートできるというテクニックです。
※「ツイートと返信」には表示されますし、検索には引っかかります。

@tosで何故タイムラインに表示されないかの考察

TwiiterのURLの構成は以下のようになっています。

https://twitter.com/ユーザー名

@ユーザー名を付けてリプライした場合、上記のURLにリンクが張られます。
このため、@tosを付けてリプライした場合、以下のURLにリンクが張られます。

https://twitter.com/tos

このページにアクセスすると「Twitterサービス利用規約」にリダイレクトされます。
つまり、tosというのは「Terms of service」の略で、サービス規約用のページで使われている文字列のため、ユーザーとして使用できないものと考えられます。
そのユーザーとして利用できない文字を利用してツイートすることで、一般のユーザのタイムラインには表示されないと考えられます。

@tosの代わりは存在するか

tosのほかにもhttps://twitter.com/homeなどはユーザー用のページでなくシステムのページに遷移します。そのため、tosと同様にシステムで使用されている文字列がトスツイとして使用できるか検証してみます。

  • URL
  • ユーザー名: リプライするユーザー
  • アカウントの存在:アカウント検索によるアカウントの有無
  • ツイート結果:ツイートした結果がどうなるか
URL ユーザー名 アカウントの存在 結果
https://twitter.com/tos @tos 存在しない 「ツイートタブ」には表示されないが、「ツイート」と返信タブでは表示される
https://twitter.com/home @home 存在する 「ツイートタブ」には表示されないが、「ツイート」と返信タブでは表示される
https://twitter.com/help @help 存在しない ユーザ名として認識されないため、リンクが作成されない。
https://twitter.com/about @about about 存在しない ユーザ名として認識されないため、リンクが作成されない。
https://twitter.com/help @help 存在しない ユーザ名として認識されないため、リンクが作成されない。
https://twitter.com/login @login 存在しない ユーザ名として認識されないため、リンクが作成されない。
https://twitter.com/logout @logout 存在する 「ツイートタブ」には表示されないが、「ツイート」と返信タブでは表示される

@homeと@logoutでも一見、@tosと同様の挙動になるように見えますが、@homeと@logoutはアカウントが存在しています。
そのため、現時点はトスツイは@tosで行っていた方が無難と考えられます。

Moodleのプラグインを作ってみる

目的

MoodleというeLearningシステムがあります。
PHPとMySQLが動けば、わりと簡単に導入することが可能です。

今回はこのMoodleのプラグインを作成してみます。

syntaxhighlighterを参考に作成しています。

ソースコード
https://github.com/mima3/aa_image/tree/master/moodle_plugin/filter_aa_image

機能

エディタで入力した「aa_image]~[/aa_image]の内容を画像に変換するプラグインをsyntaxhighlighterを参考に作成します。


以下のような入力を行ったとする。

[aa_image]
       ____ 
     /      \ 
   /  _ノ  ヽ、_  \ 
  / o゚((●)) ((●))゚o \  ほんとはVIPでやりたいんだお… 
  |     (__人__)    | 
  \     ` ⌒´     / 

[/aa_image]

[aa_image]~[/aa_image]に記載された内容を圧縮してコードに変換する。

e9zQoPAYgiHo%2FZ79EBSvwIUm0Q9hwNS%2F37MHrARZWiH%2BcXMfWE%2Fz3vd7l8SDhGDqgIx%2Bhfz3%2B%2BZrvN%2FT8Wh6%2F%2Fs9nZoKSGygTD5YMUh74%2B7HTZMfN6543Lg%2BzDPgcePyx00tj5u6HjfOf9zYApZa8Lix61HDMojRNXB3QCzqiI9%2FsmtXfDzQXGSZGphD9iB55f3eBoVHPZMObVGAiwAdysUFAA%3D%3D

このURLをパラメータとしてIMGタグを作成する。

<img src="https://needtec.sakura.ne.jp/aa_image/image?d=e9zQoPAYgiHo%2FZ79EBSvwIUm0Q9hwNS%2F37MHrARZWiH%2BcXMfWE%2Fz3vd7l8SDhGDqgIx%2Bhfz3%2B%2BZrvN%2FT8Wh6%2F%2Fs9nZoKSGygTD5YMUh74%2B7HTZMfN6543Lg%2BzDPgcePyx00tj5u6HjfOf9zYApZa8Lix61HDMojRNXB3QCzqiI9%2FsmtXfDzQXGSZGphD9iB55f3eBoVHPZMObVGAiwAdysUFAA%3D%3D"/>

コードの説明

ファイル名 説明
filter.php 出力前にコンテンツを自動的に変換する処理を記述。詳細はFiltersを参照
settings.php 設定画面の実装。詳細はsettings.phpを参照
thirdpartylibs.xml 使用しているサードパーティのライブラリの情報を記載するXMLファイル。詳細はthirdpartylibs.xmlを参照
version.php バージョン情報。詳細はversion.phpを参照
classes/privacy/provider.php 個人データを扱うか扱わないという情報をMoodleに通知するためのインターフェイス。詳細はPrivacyAPIを参照
lang/en/filter_aa_image.php 多言語対応用のファイル。画面に表示する文字列を各言語毎に用意する

settings.php

https://github.com/mima3/aa_image/blob/master/moodle_plugin/filter_aa_image/settings.php

設定画面は以下のような実装を行います。

if ($ADMIN->fulltree) {
    $setting = new admin_setting_configtext(
        'filter_aa_image/service_url',
        new lang_string('serviceurl', 'filter_aa_image'), 
        new lang_string('serviceurl_desc', 'filter_aa_image'), 
        'http://needtec.sakura.ne.jp/aa_image/image');
    $settings->add($setting);
}

この実装を行った設定ページは以下のように表示されます。

admin_setting_configtextは設定画面にテキスト入力できるコントロールを追加します。
第一引数の「filter_aa_image/service_url」は設定の名前を指定します。以下のような書式になります。

プラグイン名/一意の名称

第二引数はテキストの横に表示されるラベルの表示文字になります。
ここでは直接文字を入力することなく、lang_stringを使用して多言語対応をしています。

第三引数は説明の表示文字になります。
ここでは直接文字を入力することなく、lang_stringを使用して多言語対応をしています。

第四引数はデフォルトの値になります。

その他詳細は下記のページを参照してください。

Creating a theme settings page
https://docs.moodle.org/dev/Creating_a_theme_settings_page

filter.php

出力前にコンテンツを自動的に変換する処理を記述します。

class filter_aa_image extends moodle_text_filter {

    public function filter($text, array $options = array()) {
        if (!is_string($text) || empty($text)) {
            return $text;
        }

        $re = "~\[aa_image\](.*?)\[/aa_image\]~isu";
        $result = preg_match_all($re, $text, $matches);
        if ($result > 0) {
            foreach ($matches[1] as $idx => $code) {
                $code = str_replace(
                    ['>','<','<pre>', '</pre>', '<p>', '</p>', '<br>', ' '],
                    ['<', '>','', '', '', "", "\n", " "],
                    $code);
                $key = base64_encode(gzdeflate($code, 9));
                $newcode = '<p><img src="' . get_config('filter_aa_image', 'service_url') . '?d=' . urlencode($key). '"/></p>';
                                $text = str_replace($matches[0][$idx], $newcode, $text);
            }
        }

        return $text;
    }

moodle_text_filterを継承したクラスでfilterメソッドを実装することで実現できます。
やっている内容は[aa_image]で始まって[/aa_image]で終わる箇所を抜き出して置き換えをしています。
設定画面で設定した内容はget_configを使用して取得できます。

プラグインのインストール方法

下記のフォルダをZIP形式で圧縮します。
https://github.com/mima3/aa_image/tree/master/moodle_plugin/filter_aa_image

この圧縮したZIPをmoodleにアップロードします。

管理者でログインした後にサイドメニューの「サイト管理」を選択後、「プラグイン」タブを選びます。そこから「プラグインをインストールする」を選びます。

ZIPパッケージにZIPファイルをドラッグアンドドロップしたのちに、「ZIPファイルからプラグインをインストールする」を選択します。

警告の内容を確認し、問題なければインストールを続けます。

moodleデータベースを更新します。

更新結果が表示されるので成功の場合は「続ける」を選択します。

プラグインの管理画面が表示されるので必要に応じて設定を変更して「変更を保存する」を選択します。

この時点でプラグインのインストールは完了しましたが、プラグインの機能は有効化されていないません。

プラグインの有効化の手順

管理者でログインした後にサイドメニューの「サイト管理」を選択後、「プラグイン」タブを選びます。そこから「プラグインの概要」を選びます。

プラグイン名で検索して、そのカテゴリーの「設定」ボタンを押します。今回の場合はテキストフィルタの設定になります。

プラグイン名で検索して「有効?」の列をONにします。

プラグインのアンインストール方法

管理者でログインした後にサイドメニューの「サイト管理」を選択後、「プラグイン」タブを選びます。そこから「プラグインの概要」を選びます。

プラグイン名で検索してアンインストールを実行します。