銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

AsciiDocで文書内の任意の場所にリンクする

f:id:takamints:20200627135648j:plain
photo credit: verchmarco Open book with a cone symbolising the snug time of the year staying inside and reading via photopin (license)

正式な文書はAsciidocで書いてPDFを出力することが多くなっています。 しかし、いつでもAsciidocを書いているわけでもありません。コードも書かなきゃならんし、メモ書き程度ならマークダウンで充分だし。

「さあ今日はガッツリ設計書を書きますよ~」って時に、あれ?リンクを貼るのってどうするんだっけ?といったことがよくあります。というか毎回そうなっている。ぼんやりとは覚えているけれど、正しくレンダリングされないとか。

こんな時は、すぐに検索するのですが、Asciidocの書き方を説明してくれてるページって、「見出しはこうする。テーブルはこう書けば良い・・・」みたいな感じが多くて、いろんな記法の説明がたくさん詰まっているもんで、いま知りたいことが、すぐわからない。

しかし文句ばっかり言ってちゃダメです。無いなら自分で書いときましょう。一旦書いたら割と覚えておけたりするもんで・・・。

てなことで、ここでは「文書内の任意の場所に飛べるリンクの書き方」を書いています。 外部の文書や、見出しへの自動リンクについてはまた別の機会に。

目次

概要

AsciiDocで、文書内の任意の場所にリンクを貼るには、 リンク先に「アンカーID」を設定し、 リンク元では「アンカーID」と「リンクテキスト」を指定します。

リンク先に アンカーID を設定する

リンク先に アンカーID を設定するには、その直前の行の行頭から、 [[アンカーID]] と記述します。

[[anchor-id]]
この段落がリンク先です。この直前に anchor-id が設定されています。

文書内へのリンクを記述

AsciiDocで、文書内の任意の場所にリンクを貼るには、 「<<アンカーID,リンクテキスト>>」 と記述します 。

リンクを貼るには、<<anchor-id,このように>> 記述します。

アンカーID は、 リンク先に設定しておく識別子です(「アンカー(anchor)」はイカリの意味)。 日本語でも構わないようですが、 私はなるべく英数字+ハイフンなどを使っています。 検索する時に便利なので。

文書内リンクの例

リンクを貼るには、<<anchor-id,このように>> 記述します。


[[anchor-id]]
リンク先の文章です。

アンカーID は、文書内で複数同じものが設定されていてはいけません。 複数あると、どこにリンクすればよいかわかりませんから。

JavaScriptのArray.fillで気をつける事

f:id:takamints:20200512125305j:plain
photo credit: diana_robinson The Milky Way over a radio telescope at the Karl G. Jansky Very Large Array National Radio Astronomy Observatory in New Mexico via photopin (license)

JavaScriptで、あらかじめ一定の長さの配列に初期値を放り込んでおくために Array.fill を使いますが、勝手な思い込みからハマってしまいました。恥ずかしながら、その詳細をご報告。

目次

Array.fill はどんな関数?

Array.fill は、配列の全要素に同じ値を設定する関数です。 以下の例では、3個の数値配列を生成して値 0 で初期化しています。

const arr = Array(3).fill(0);

余談ですが、fillで初期化しておかないと、要素はすべて undefined になっており、そのままでは forEach などの列挙メソッドが回りません。

Objectで初期化したらおかしな挙動

以下のように初期値にObjectを与えていたのですが、思った通りの動きをしてくれませんでした。

const arr = Array(3).fill( { x: 0, y: 0 } );

3個の二次元座標値を持つ配列。。。のつもりですが、以下のようなことになりました。

const arr = Array(3).fill( { x: 0, y: 0 } );
arr[0].x = 10;
arr[0].y = 8;
console.log(JSON.stringify(arr));
//出力:[{"x":10,"y":8},{"x":10,"y":8},{"x":10,"y":8}]

0番目の情報を書き換えただけで、他のすべてのデータが書き換わってしまいました。 配列要素は確かに3つあるのですが、全ての要素が、ひとつの座標値を参照しているのです。

なぜこうなるか

JavaScriptのObjectはメソッドの引数や代入時には、必ず参照が渡されますから、このようなことが起こります。 ソースコードをよく見れば、fill に与えられている座標値は、1度しか生成されていません。 だから直感的でないにせよ、これは当たり前の挙動です。 つまり、ワタシが勝手に「別の複数のオブジェクトが生成される」と思い込んでいただけなのでした。

解決方法

唯一の正しい答えなんてありませんから、3つほどの解決方法を示してみます。 プリミティブな型なら最初の方法が良いと思いますが、プロパティを持つオブジェクトならば2番めの方法が好みです。 関数プログラミング的には最初の方法が正解だと思われますが。

(1) まるごと代入すれば良い

上の例の2行目と3行目を以下のように変えれば、別のオブジェクトを生成して代入していることになるので、なんら問題ありません。

const arr = Array(3).fill( { x: 0, y: 0 } );
arr[0] = { x: 10, y: 8 };
console.log(JSON.stringify(arr));
//出力:[{"x":10,"y":8},{"x":0,"y":0},{"x":0,"y":0}]

ただ、これがベストだとは思いません。 なんて言いましょうか、初期化のコードとまるごと代入部分で、統一感がないというか、思想に差があるというか、そんな妙なミスマッチ感がありますね。 最初は全要素がx,yともに0の単一オブジェクトを参照していますが、その後、値としては同じ(x,yとも0である)データを代入したなら、別のインスタンスを参照することになる。

あと、誰かがうっかり別のところで要素の「プロパティを書き換えちゃった」というような不測の(しかし予測可能な)事態に対して脆弱です。構文的な防御策がありませんからね。

加えて、多くのプロパティを持つオブジェクトである場合、インスタンスの生成コストも気になります。本来、気にするべきではないのですが。

(2) 別オブジェクトで初期化する

初期化時点で別のオブジェクトを設定しておくには、以下のように書けばよろしいかと思います。

const arr = Array(3).fill().map(e=>({ x: 0, y: 0 }));

fill()fill(null) と同じです。全要素をnullで初期化しています(初期値は nullでなくても undefined 以外ならOKです)。 これをしておかないと全ての要素が undefined になっていて、うしろの map が回ってくれないのです。 こうしておけば、以下のように間違いは発生しません。

const arr = Array(3).fill().map(e=>({ x: 0, y: 0 }));
arr[0].x = 10;
arr[0].y = 8;
console.log(JSON.stringify(arr));
//出力:[{"x":10,"y":8},{"x":0,"y":0},{"x":0,"y":0}]

(3) ループを回して初期化する

また、以下のようにループを回して初期化する方法もありますが、前時代的ではありますね。

const arr = Array(3);
for(let i = 0; i < arr.length; i++) {
  arr[i] = { x: 0, y: 0 };
}

Git初心者が最初に知るべきチームでGitの使い方

f:id:takamints:20200415072108p:plain



皆さんはGitを問題なく使えているでしょうか?

時おり社内で「リポジトリが何だかおかしくなってるんです~」と相談を受けることがあります。 コミットログが思ってたのと違う状態になっていて、さらに元に戻そうとアレコレやって状況悪化。 最終的に、どうにもならなくなっちゃったって感じです。

たいていは単純にGitの操作ミスによるものです。でもGitについて正確に理解していれば、ちょっとした間違いはすぐに元に戻せるはず。 裏返せば、Gitの仕組みや概念を正確に理解できておらず、「なんとなく」Gitを使っている人が多いのでしょう。 Gitのコミットやブランチについて明確なイメージを頭の中に描けていないように思います。

ところがこの春、新型コロナの影響で、弊社も「基本的にはテレワーク」と指示が出まして不安が的中。既にちらほら問題が発生してます。

ということで「Gitが不安、難しい」と思っている人たちのために、最初にキチンと理解しておいて欲しいことや、基本的な使い方を書いておきます。 ちなみに、Gitを難なく使っている人にとっては当たり前のことしか書いていません。


目次


masterブランチで作業しないで

いちいち「この作業はブランチを切って行えばよいですか?」って聞いてくる人がいますが、これ当たり前なんですよね。 なにか作業するにはワーキングブランチを作成して、そのブランチで行ってください。

ワーキングブランチのベースをどこにするかは、採用しているフローに依存します。 弊社では GitHub Flow でやりましょうとなっているのでベースは必ず master です。 この点についてはチーム内で取り決めがあるはずですし、なければ取り決めておくべきですね。

GitHub Flowでは、作業内容を具体的に表す名前をワーキングブランチに付けるべきだとなっています。 そして早い段階で(コードを変更していなくても)リモートにpushしておきます。 これによって、チーム内に「自分が何をしようとしているかを知らせる」ためです。

いろんな変更を詰め込まないで

たくさんのコードを変更して、一発巨大なコミットをぶちかますのはやめてください。 また、ひとつのワーキングブランチで、色んな種類のコミットを積み重ねるのもやめてほしい。ワーキングブランチの変更内容は首尾一貫している必要があります。

その時の作業に無関係な部分で「あら、ここのインデントおかしいやん」とたまたま発見しても、そのまま流れで修正しないでほしいのです。 これをやると、後のmergeやrebaseでCONFLICTが発生しやすくなって、割ととんでもない手間がかかります。 本筋から外れた問題を見つけたときは、忘れないようにIssueを書いて、本来の作業に戻りましょう。

これらを正しく行うには、キチンと作業の計画を立て、短期のゴールを見定めて、それに集中する必要がありますよ。

masterブランチをpushしないで

複数人のチームで開発しているとき、masterブランチを前に進めるのはプルリクだけに限定したい。 緊急のBugFixなどでは、この限りではありませんが、少しでも時間的に余裕があるならプルリク投げてマージしたい。

主な理由はレビューできないからですが、チーム内に「masterが前に進む」ということを周知できるのも理由のひとつ。

同じ場所で作業している場合に、声を掛け合ってワーキングブランチをローカルでmasterにマージして、pushするケースは確かにあります。プロジェクトの初期段階では特に。 でも、リモートワークでメンバーがバラバラな状況で、さらにGitの操作に慣れていない人がいる場合などでは、正統にプルリクを投げてレビューしてからマージするのが筋というものです。

複数のコミットが連なったブランチを直接(プルリクを投げないで)レビューする時、レビュアーはレビュー対象のブランチを手元にチェックアウトしてHEADとベースブランチの差分を確認する必要があります。 GitHubなどのサービスでは複数コミットの差分をまとめて確認しにくいので。

他人のワーキングブランチにコミットしない

他の人のワーキングブランチをチェックアウトするのは構いません。コードを確認したりするだけですから。 でも、そのブランチに変更を加えてコミットした上で、push しないで欲しいのですよ。

これって論外だと思うのですが、おそらくワーキングブランチとは何であるかを理解できていないのでしょうね。

ワーキングブランチは、それを作った人の責任においてコーディング作業が進んでいるものであって、途中から無言で他の人が別の変更を加えちゃうと、その時点で枝分かれしてしまいますよね。 ブランチのオーナーは最終的に複数のコミットをまとめようとしているかもしれませんし。 とにかくワーキングブランチは途中段階の中途半端な状態なので、本人から移譲されたのでない限り、勝手に変更を加えるようなものではありません。

他の人のワーキングブランチが自分にとって必要であるならば、きりの良いところでプルリクを投げてもらってmasterへマージしてもらうように依頼するべきです。 そして、そのプルリクがマージされたあとに、自分のワーキングブランチへマージするべきです。いずれにせよ無言でしれっとやるのはご法度です。

ワーキングブランチを早い段階でリモートにpushしておくのは「私はこういう作業をしていますよ」ということをチーム内に表明するためです。

今まで未遂も含めて何度か経験しています。 一度はそのブランチがmasterにマージされていて驚きました。 怖いよ初心者w。まあその後問題が出なかったので良かったけれど。

rebaseしないでmergeして

ワーキングブランチで作業中に、リモートのmasterが前に進んでいることがあります。 このとき自分の作業が終わってプルリク投げる時などに、新しいmasterにrebaseする(ワーキングブランチのベースブランチを新しいmasterに切り替える)のはやめて欲しいのです。

じゃあどうするか。その時点でコンフリクトが発生する可能性があったり、新しいmasterに加えられた変更が自分の作業に必要であったりするなら、新しいmasterをワーキングブランチへmergeすれば良いのです。 逆に、明らかにコンフリクトが発生しない場合や、自分の作業に無関係な変更であるなら、何もしなくても構いません。

コンフリクトが発生するかどうかの判断は、少し難しいかもしれません。 なので同じファイルを変更しているかどうかで判断すれば良いと思います。 同じファイルを変更しているなら良いタイミングで新しいmasterをワーキングブランチにmergeする。 そうでないなら何もしなくてOKです。

rebaseをやめて欲しい理由のひとつは「際限がない」からです。 複数人で開発している場合、作業中にどんどんリモートのmasterは進んでいくと思います。 その時に、いちいちrebaseするのは手間ですし、コンフリクトが発生しそうなときだけrebaseするという対応も、状況によってベースブランチが変わるなんて、おかしな話です。

ちなみにmasterとの間でコンフリクトが発生したら、その解消時に尊重されるべきなのはmasterブランチのコードです。 masterブランチに取り込まれた時点で、それは「みんなが認めた正統なコード」ですからね。

プルリクのマージを急かさないで

プルリク投げてマージを急かす人がいますけど、あれも困ります。

あとの作業にそのブランチがマージされる必要があると懇願されたことがあるのですが、それってプルリク投げるタイミングでないかもしれません。 切りの良いところで、一旦プルリクを投げておきたいのであれば、その後も同じブランチで作業すればよいのです。 そのブランチでの作業はまだ終わっていないということですから。

勝手に安易にリファクタリングしないで

全体の構成を変更したり、命名規則を修正したりするリファクタリングに類することは広範囲に影響を及ぼしますから安易にこっそりやるべきではありません。 チーム内に注意を促してタイミングを見計らう必要があります。

また個々のコミットの変更内容を、極力あとでリファクタリングする必要がないように気をつけるのも必要です。

React:非同期の副作用フック(useEffect)で正しくクリーンアップする

f:id:takamints:20200319064019j:plain

Reactの関数コンポーネントで非同期の副作用フックを正しく記述する方法を書いています。

非同期処理でコンポーネントを更新する場合には、副作用フックを使用する必要があり、この副作用フックからはクリーンアップ関数を返却しておく必要があります。 REST APIなどの非同期処理によってデータを取得し画面に反映する場合などには、非同期の結果待ちの間に更新先の画面がなくなっている(DOMがアンマウントされる)かも知れないためで、それに対処しなければなりません。

副作用フックに対して、更新先の画面がなくなったことを伝えるための関数がクリーンアップ関数で、このクリーンアップ関数は副作用フックが返却する関数オブジェクトです。 (なんだか話が前後しているようでややこしいですが、この動作機序を理解するには、JavaScriptの非同期処理の動作、関数オブジェクト、そしてクロージャについての知識が必要かと思います)

目次


副作用フックについて

副作用フックは、関数コンポーネントの中で利用できるフックです。 DOMがマウントされて画面が更新されたあとに呼ばれる関数を登録します。

この関数内で、何らかの条件によって、コンポーネントのステートを更新すれば、再び画面が更新されるという仕組みです。

画面が更新されると、また同じ副作用フックを登録することになりますから、無限に更新を繰り返すことは避ける必要がありますね。

非同期動作する副作用フック

この関数内で、例えばREST APIを呼び出して、非同期でデータを取得してステートを更新すると、再び画面を更新することになるのですが、既にユーザー操作によって別の画面に遷移していると、更新すべきコンポーネントがなくなっているかもしれません。

アンマウントされたDOMに対してステートを更新すると「メモリリークしてるかも!」という警告が表示されます。

つまり、DOMがアンマウントされているときにはステート更新を行わないようにするべきなのです。

クリーンアップ関数でDOMのアンマウントを知る

そこでDOMがアンマウントされたことを知るために、副作用フックの「クリーンアップ関数」を使用します。 クリーンアップ関数は副作用フックが返す関数です。この関数は副作用フックが呼び出されたあと、DOMがアンマウントされた時に呼び出されます。

実行順序をざっくり書けば、

  1. 画面がマウントされ、
  2. 副作用フックが呼ばれ、
  3. 副作用フックがクリーンアップ関数を返し、
  4. DOMがアンマウントされたときに呼び出される

ということになります。

つまり、非同期動作を待っている(await中の)副作用フックに対して、既に返したクリーンアップ関数にDOMのアンマウントが通知されるということです。 非同期動作が完結したとき、既にクリーンアップ関数が呼ばれていたならDOMがアンマウントされているのでステート更新を行わなければよいのです。

正しくない非同期関数コンポーネント

副作用フックを使わない(動くけど正しくない)

一方、非同期でのステート更新は副作用フックでなくても一応動きます。 以下は副作用フックを使っていない不完全な非同期関数コンポーネントなんですが、一見正常動作は可能です。 しかし、既に上に書いたように、非同期処理が完了するまでの間にユーザー操作によって別の画面に遷移したとすると「メモリリークの可能性がある」という警告が出ます。

import React from "react";
export default function AsyncComponent() {

    //非同期で取得するデータ
    const [result, setResult] = React.useState(null);

    //非同期無名関数の即時呼び出し
    (async ()=> {

        //非同期でデータを取得し、ステート更新
        const result = await getAsyncData();//架空の関数
        setResult(result);

    })();

    return (
        <div>
            { result ? <p> { result } </p> : <p> loading... </p> }
        </div>
    );
};

関数コンポーネント自体をAsyncにしちゃう(動かない)

どこにも書いていなかったようなので、念の為に試してみたのですが、 関数コンポーネント自体をAsync関数にした場合、ビルド時エラーは出ませんが、まったく動きませんでした。

import React from "react";
export default async function AsyncComponent() {
    const [result, setResult] = React.useState(null);
    setResult(await getDataAsync());
    return (
        <div style={{textAlign: "left"}}>
            {
                markdown ? 
                    <article dangerouslySetInnerHTML={{__html: markdown}}/>:
                    <p>loading...</p>
            }
        </div>
    );
};

副作用フックをAsyncにしちゃう(できない)

Async関数は必ずPromise オブジェクトが返します。 しかし先に書いたように、副作用フックはクリーンアップ関数を返さなくてはなりません。 だから副作用フックの関数自体をAsync関数にはできません。

正しい非同期副作用フックとクリーンアップ関数

コンポーネントがアンマウントされたかどうかを知るために副作用フックを使います。 副作用フックの戻り値は「クリーンアップ関数」と呼ばれる関数オブジェクトで、後にコンポーネントがアンマウントされたとき呼び出してもらえます。

以下のコードで示すように( React.useEffect の部分)。

import React from "react";

//非同期関数コンポーネント
export default function AsyncComponent() {

    //非同期で取得するデータ
    const [result, setResult] = React.useState(null);

    // 副作用フック
    React.useEffect(()=>{
        let unmounted = false;

        //非同期無名関数の即時呼び出し
        (async()=>{

            //非同期でデータを取得
            const result = await getAsyncData();//架空の関数

            //アンマウントされていなければステートを更新
            if(!unmounted) {
                setResult(result);
            };

        })();

        //クリーンアップ関数を返す
        return ()=>{ unmounted = true; };
    });

    return (
        <div>
            { result ? <p> { result } </p> : <p> loading... </p> }
        </div>
    );
};

副作用フックの最初で unmounted という変数を初期値falseで宣言しています。 これは非同期処理中にコンポーネントがアンマウントされたかどうかを保持します。 最初の時点ではアンマウントされていません。

次に、関数の内部で無名のAsync関数を即時呼び出ししています。 この関数の中で非同期にデータを取得したあと、アンマウントされていなければステートを更新しています。

最初に宣言された unmounted は、この副作用フックが返す「クリーンアップ関数」で true に更新されています。 つまり非同期処理が完了する前にコンポーネントがアンマウントされると、ステートは更新されませんから、メモリリークの心配はありません。

VS-Codeで「NPMスクリプトが見つかりません」の対処法

f:id:takamints:20200309193608p:plain


徹底解説Visual Studio Code
本間咲来
シーアンドアール研究所 (2019-09-27)
売り上げランキング: 22,236

テキストエディタVim/GVimを使い続けてきましたが、ここ半年ほどで完全に Visual Studio Code へ移行しました(ただしVim拡張機能は使っています)。

移行理由はNPMスクリプトをワンクリックで実行できる機能がとても楽なんですよねえ。

コードの編集中はたいていテストをwatchモードで走らせっぱなしです。


目次

ところが「スクリプトが見つかりません」

ところが、ある時からサイドバーのnpmスクリプト一覧に「スクリプトが見つかりません」と表示され、まったく使えなくなってしまいました。

f:id:takamints:20200206112430p:plain

何かのスクリプト実行時に「なんだか動作が遅いから、なにかの設定をOffにする?」と、VsCodeがメッセージボックスを表示してきました。 信頼するVsCodeさんが言うことなので「それならば…」とAcceptだかYesだかOKだかのボタンを押してしまったんですね。うかつでした。

で、その時以来 サイドバーの NPM SCRIPTS に「スクリプトが見つかりません」と表示されて実行できなくなりました。 さらにそのOFFにした設定項目がなんだったのか忘れてしまって戻せない・・・。

設定項目「タスク自動検出」のせいでした

この現象を検索しても復旧方法がわかりません。こういう状況に陥っているのは自分だけなのかとチョット孤独。 ほぼ同じ機能の拡張機能があるので、仕方なくそれを利用していましたが、少々使い勝手が違っていて、少し不便なんですよねえ。 そう思いながら半年ぐらい使い続けていたのですが、先日、設定項目を眺めていると「これ?」というのを見つけました。

それが「タスク自動検出(Task: Auto Detect)」でした。初期値はONらしいのですが、これがOFFになっていました。 基本的な設定はよっぽどでないと初期設定から書き換えないで使うので発見できた。

f:id:takamints:20200206112558p:plain

ということで「タスク自動検出(Task: Auto Detect)」をONにしてみると、見事解決しました。

「タスク自動検出(Task: Auto Detect)」の設定方法

メインメニューから [File]-[Preferences]-[Settings] と辿って、設定項目の検索ボックス([Settings]の[Search settings])に「task auto detect」などと入力すると「Task: Auto Detect」が表示されます。

f:id:takamints:20200206112721p:plain

これを on に変えて、NPMスクリプト一覧の更新ボタンをクリックすると、 問題なくnpm scripts を見つけてくれるようになりました。

f:id:takamints:20200206112834p:plain

おめでたい。

あとがき

メッセージボックスによる通知や提案って、ありがたくて便利なのですけど、慎重に内容を検討しないと、あとからそれが何だったのかわからなくなりますね。

VsCodeはとても便利ですが、いろんな局面で不意にポップアップが表示されて少しおせっかいだと感じることがあります。 そのおせっかいが標準なのか拡張機能のせいなのか?とか、どこかの設定でどうにかなるの?というのもわかりにくい。

まあ、愚痴っていても仕方がないから、時間のある時に設定項目などをじっくり眺めてみるのが良いのかもしれないですね。

これってなにが悪いんだろうね。よくわからないままOFFにしちゃった自分も悪いのは確かなんだけど、UI的に問題ありそうにも思ってる。