お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
投稿日:
言語::C#OS::Linux::Ubuntuジャンル::セットアップ

インストール

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script

wget https://dot.net/v1/dotnet-install.sh
chmod 755 dotnet-install.sh
./dotnet-install.sh
echo 'export DOTNET_ROOT=$HOME/.dotnet' >> ~/.zshrc
echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >> ~/.zshrc

Hello world

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet

dotnet new console -o myapp
dotnet build myapp/myapp.csproj
./myapp/bin/Debug/net6.0/myapp
投稿日:
Node.js::Prettier言語::Markdown

Prettier の次期 Major release である v3 がそろそろ出そうなので、今回含まれる大きな変更の一つについての記事です。

内容としてはMarkdown文書の全角文字と半角英数の間にスペースが挿入されなくなります。個人的にこれは相当賛否が分かれると思うので、v3が出た後の対処方法も併せて書いておきます。

変更点

以下は漢字Alphabetsひらがな12345カタカナ67890という文字列が渡された時の変化例です。

v3 より前 v3 以降
漢字 Alphabets ひらがな 12345 カタカナ 67890 漢字Alphabetsひらがな12345カタカナ67890

多分スペースが空いたときのほうが好みといった人が一定数居ると思います。個人的には検索性が破滅的に低下するためこの機能には否定的でしたが、今回のアップデートで遂にスペースが入らなくなったので、割と喜んでいます。ぶっちゃけこのブログの過去記事ににある余計なスペースも現在全て除去する作業をしてるくらいです。

発端としては次のIssueにあり、これをずっと追っていた人もきっといると思います。私もそのうちの一人です。

この問題は次のPullRequestで対応され、現在v3 用のブランチに取り込まれています。

  • [Markdown[next branch]: Do not insert spaces between Chinese/Japanese & latin letters #11597](https://github.com/prettier/prettier/pull/11597)

この機能を試したい場合、以下のようにインストールすることで試すことが出来ます。

npm i -D https://github.com/prettier/prettier.git#next

なお、PullRequestにある通り既にあるものには影響しないので、既にあるスペースを詰めることは出来ません。

これまで通りスペースを入れたい場合

引き続き漢字Alphabetsひらがな12345カタカナ67890漢字 Alphabets ひらがな 12345 カタカナ 67890にしたいケース

前述のPullRequestの中でも言及されていますが、textlintを利用することで対応できます。ただPrettierと併用するのは難しいと思います。

導入方法

textlintにtextlint-rule-preset-ja-spacingを組み合わせて対応します。

npm i -D textlint textlint-rule-preset-ja-spacing
使用方法

まず.textlintrcを作成し、次の設定を追加します。

{
    "rules": {
        "preset-ja-spacing": {
            "ja-space-between-half-and-full-width": {
                "space": "always"
            }
        }
    }
}

CLIを用いて修正する場合

npx textlint --fix <path>

VSCodeを用いる場合、vscode-textlintをインストールし、settings.jsonに以下の設定を追加することで利用することが出来ます。Prettierと併用する場合、Prettierと競合し、textlintが負けるため、Markdownの整形に関してはtextlintに任せるのが無難だと思います。

{
    "textlint.autoFixOnSave": true
}

一応Custom Local Formattersを使えば近いことは出来なくもないのですが、ファイルへの書き込み同期がズレてるのか上手く動かない気がします。一応以下の設定で動くことには動きますが、あんま期待しないほうがいいかも…。拡張からコマンドを蹴ること自体セキュリティ的に微妙な気がするので、この方法は使わないのが無難な気がします。

{
    "[markdown]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "jkillian.custom-local-formatters"
    },
    "customLocalFormatters.formatters": [
        {
            "command": "npx prettier ${file} | npx textlint --stdin --stdin-filename ${file} --fix --format fixed-result",
            "languages": ["markdown"]
        }
    ]
}

これはPrettierとtextlintを呼ぶ時点で二重処理になって重いので、Markdownのフォーマットはどっちか片方にやらせたほうが良いでしょう。ただtextlintはデフォルトだと表の整形とかいい感じにしてくれないようなのが困りものです。textlintにmarkdownプラグインを入れると改善されるかもしれませんが試してないです。

これまでのスペースを消したい場合

既にある漢字 Alphabets ひらがな 12345 カタカナ 67890漢字Alphabetsひらがな12345カタカナ67890にしたいケース

前項の「これまで通りスペースを入れたい場合」で紹介した環境を導入し、.textlintrcを以下のように作成すればある程度は対応できます。但し完全には除去しきれません。

{
    "rules": {
        "preset-ja-spacing": {
            "ja-space-between-half-and-full-width": {
                "space": "never"
            }
        }
    }
}

この設定によるtextlintの整形結果は以下のとおりです。記号の前後の半角スペースや見出しやリンク文章にある半角スペースは除去しきれないようです。

 ## 123 あああ abc

-ユーザー ID は Slack アプリから
+ユーザー IDはSlackアプリから
 やよいの ○ 色申告
 [Web サイトの example](https://example.com)

余談

多分大抵の人はPrettierをv3にしてからこの事に気が付くと思います。当然です。一々 OSSのアップデートを追っている人は恐らくメンテナか物好きだからです。

現在Prettierのリポジトリでは似たような議論として、デフォルトでインデントにスペースを使っているところをタブに置き換えようという動きがあります。幸いv3では採用に至っていませんが、今後採用される可能性があり、メンテナであるSosuke Suzuki氏もこの事について以下の記事で憂慮されています。

ある日突然インデントのスペースがタブに変われば、苦情が出るのは明白です。設定一つ直すだけとは言え、影響は大きいでしょう。CIで全体にPrettierを掛けていないプロジェクトでは一時的にタブインデントのファイルとスペースインデントのファイルが混在するケースもあると思います。空白文字の差分表示や検出を無効化していると気づかないまま、そのような状態になるケースもあると思います。

個人的にはMarkdownに落とした時にどの環境でも同じように見えるという理由でスペースインデントが好みですが、A11yの観点からタブにせよという考えも理解は出来ます。でもブラウザで.mdファイル見たときとかに見づらいんですよね…。PythonPHPYAML の様に標準がスペースインデントの言語もありますし、個人的には標準はスペースで構わないのではと思ったりもします。(その上で必要に応じてタブを使うという選択肢は出来ると思います。Google JavaScript Style Guideでも、これが標準です。Prettierは代表的なフォーマッタで事実上のコーディング標準であるとも言えるため、社会的責務でそうする必要があるかもしれませんが、個人的な思いとしては標準はスペースのままであってほしいなぁと思います。どうしても標準が変わると、世間はそこに引きずられざれ得ないと思いますから。

多分今回の変更も、上記のuseTab同様に一部で非難が上がる気がしていて、余り大事にならなければいいなぁ…とか考えています。(タブと比べると影響範囲が狭いので、ひょっとしたら今回はあまり何も起きないかもですが…)

投稿日:
言語::HTML言語::CSS

Bioサイトを作っていた時にimgタグの下に謎の隙間ができることに気がついたので、その対処録。

問題

画像の下に謎の隙間があり、DevToolsで見るとaタグがはみ出ているように見える。marginpadding0にしても効果なし。

imgタグの下に隙間がある

aタグがimgタグの下にはみ出ているように見える

aタグを外して、imgタグ単体にしても起きる。

aタグを外し、imgタグ単体にしても起きる

再現コード例

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>lycolia.info</title>
  </head>
  <body>
    <a href="https://example.com">
      <img src="example.png" width="200" height="40" alt="example">
    </a>
  </body>
</html>

原因

imgタグはinline要素なので vertical-align の既定値が baseline になっている関係で、文字の下端と画像の下端が合う状態になっているために、隙間が生まれていることが確認できる。

文字の下端と画像の下端が合う状態になっている

解決策

imgタグに対してvertical-alignbottomないしtopを指定すれば解消する。

vertical-alignaを指定したときの例、下の隙間がなくなっている

勿論この状態で横に文字を並べると、その文字は当然浮くが、今回はこの解決方法は扱わない。

横に文字を並べた例、空白分文字がずれている

参考コード

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>lycolia.info</title>
  </head>
  <body>
    <a href="https://example.com">
      <img style="vertical-align: bottom;" src="example.png" width="200" height="40" alt="example">
    </a>
  </body>
</html>
投稿日:
言語::C#開発::テスト

Windows向けのツールを作ろうというので、昔よく使っていたC#.NETで開発をすることにしたのですが結構苦労があったのでその話を書いていこうと思います。Unit testingを書くのは一旦断念しましたが、暇があれば挑戦したいとは思ってます。

事のいきさつ

WindowsのVSCodeでRemote Development拡張を使うことが多いのですが、リモートエクスプローラーにアイテムが増えてくると移動したいフォルダを探すのが大変になってくるのと、ここに出てないフォルダを開くのが結構手間という問題があり、これを解決できないかということを考えていました。

そこで考えついたのがWSLやSSHのネットワークパス上でExplorerの右クリックメニューを開いたらVSCodeがリモートモードで起動できると良いのでは?という所でした。Windows向けでexeからバイナリ起動するならC#で書くのが楽だろうと考え、C#での開発に着手しました。

疎結合な実装でUnit testingもできたらいいなーと漠然と考えながら開発していたのですが、最後にC#を書いたのは3年ほど前で、C#でモダンな実装をした経験もなかったので結構苦労しました。よく考えたらTypeScriptかJavaScriptくらいでしかまともにやったことがないし、UT自体他言語でもGolangかPHPでしか書いたことがなかったので、C#でこれをやるのは自分の実装技術に対する一種の挑戦みたいなところがありました。結論としてはあっけなく破れたわけですが…。

ツールの要件

  • Explorerの右クリックメニューから起動する
  • 開いたパスに応じてWindowsローカル、SSH、WSLを判定し、適切なモードでVSCodeを起動する
  • パス解決には設定ファイルを用いる
    • Explorerから渡されるパスはリモート環境のパスと差異があるので、そこを解決するためのものです

ツールの設計

やることは設定ファイルを読み込み、コマンドライン引数から値を取得し、それらをいい感じに変換してVSCodeに渡すだけです

大まかなフロー

大まかな動作フロー

実行形式

  • コマンドライン引数
    • this.exe <dir path>
  • 設定ファイル形式
    ```json5
    {
    "CodePath": "VSCodeのexeパス",
    // リモートホスト定義。辞書形式
    "Remote": {
    "リモートホスト名": {
    "ExplorerPrefix": "explorerのパスから除外する文字列",
    "AppendPrefix": "explorerのパスに追加する文字列"
    },
    }
    }
    ```

ツールの実装

まずはTDD的にやろうとしてInterfaceを作成し、Classで実装し、ロジック自体は好調に組み上がっていき、テストもすべて通るようになりました。但し実際にexeを蹴ると動かず、テストは通るが動かないゴミが出来上がっていました。(途中で動作確認をしなかったのか?と思うかもしれませんが、実際は完全に動くものを作ってからテスト可能な形に書き換えるという流れで作っていたのでこうなっています)

動かなかった理由は設定ファイルにJSONを採用していて、デシリアライザにSystem.Text.Json.JsonSerializerを使っていたためです。このデシリアライザは標準ではInterfaceに対してJSONをデシリアライズできません(考えてみれば当然ですが)。しかし、テストをするためにはモックをDIしたいので、Interfaceが必要です。

以下は実際に設定を読み込むために実装したClassですが、メンバが全部Interfaceになっているので、標準の状態ではデシリアライズに失敗します。ならばデシリアライザを自作すればいいという話が出てくるのですが、高々設定ファイルを読み込む処理にそこまで情熱を込めるか…?と言うことになり、諦めました。

public class ConfigBase : IConfigBase {
    private IFileInfo? _CodePath;
    private IDictionary<string, IConfigRemote>? _Remote;

    public IFileInfo CodePath {
        get {
            if (this._CodePath == null) {
                throw new Exception("Config Error: Missing CodePath.");
            } else if (this._CodePath == null) {
                throw new Exception("Config Error: CodePath is empty. Set the code.exe path in this field.");
            } else if (!this._CodePath.Exists) {
                throw new Exception("Config Error: CodePath is Not exists.");
            } else {
                return this._CodePath;
            }
        }
        set { this._CodePath = value; }
    }

    public IDictionary<string, IConfigRemote> Remote {
        get {
            if (this._Remote == null) {
                throw new Exception("Config Error: Missing Remote.");
            } else if (this._Remote.Count == 0) {
                throw new Exception("Config Error: Remote is empty. Set the remote infomation in this field.");
            } else {
                return this._Remote;
            }
        }
        set { this._Remote = value; }
    }
}

そもそもこのツールは「設定ファイルを読み込み、コマンドライン引数から値を取得し、それらをいい感じに変換してVSCodeに渡すだけ」のツールです。たったそれだけのツールに入れる仕組みにしては大げさすぎると感じました。

テストをするためにInterfaceを作ったり、本実装とモック用のClassを作る程度まではまだ容認できるのですが、カスタムデシリアライザを作ると、今度はそれのテストも必要になってきます。どう考えてもしんどい。

因みにこのコード、仮にInterfaceをやめても文字列をFileInfoに組み替えるためのカスタムデシリアライザの実装が必要で結構頭が痛くなります…。多分そこはFile.existsをラップしたクラスをDIしてやるのが無難な気がしますね。

この辺はTypeScriptだとimportの中身をJestで書き換えたら終わりなのであんま考えなくていいのは楽ですが、C#だと厳しいなと感じました。

C#でテストを書いていて思ったこと

クラスベースで実装していくとメソッド単位でのテストがしづらく、テストがコケても原因が把握しづらいというのを一つ課題感として覚えました。TSでなんちゃって関数型開発をしていれば関数のUTを書けば関数の挙動を把握できますが、Classではそうも行きません。

仮に1つのpublic methodが5つのprivate methodを呼び出していて、private methodの裏ではprivate propertyが複雑な依存を持っていたとしたらどうでしょうか?
正直デバッグをかけないとどこで何がコケたか特定できないと思います。そんな実装にするのが悪いといえばそうですが、でもクラスってそういうものじゃないですっけ…?お互いに関連性がなくていいならもうそれ関数で良くないですか?って思いました。

勿論、全部静的メソッドにしてClassそのものは単なるエンティティにするのも選択肢の一つだとは思います。しかしC#でそこまでやるか…?という疑念が個人的にあるのと、処理を繋げたテストをUTとして書く方法がなくなると思います。例えばインスタンスメソッドならモックをDIすることでメソッドが呼ばれた事や、戻り値に対する分岐を確認できますが、静的メソッドでこれをやるのは難しいと思います。

Classの単体テストは関数と状態が密結合したテストになってしまうので、いまいち微妙だなと思ったのが今回思ったことでした。