この記事では学術的正しさについて説明しない。そういう記事はほかにあるから。単に書きっぷりについて話す。

なぜか?それは、初心者って書き方に迷うから。

本来は厳密なコードの書き方をするべきっていうか、コードの意味を理解して完璧なコードを書くべきではある。だが、完璧な状態からあえて崩すこともあろう。リスクやデメリットを承知したうえで、メリットが勝るんなら崩す。問題を解決するためにな。解決すべき問題を解決するのがエンジニアですから。

◆awaitすんな

public async Task<int> Piyo()
{
    return await Task.FromResult(1);
}

これ。よく見かける。例えばDBやらHTTPやらの通信するときって、Asyncメソッド使うじゃん?そのデータアクセス処理を切り出したのは偉いんだけど、asyncメソッドにしながらreturnで一度だけawaitしておる。

こう書こうよ。

public Task<int> Piyo()
{
    return Task.FromResult(1);
}

Taskを持ってんだから、そのままTaskとして返却すりゃいいんです。

具体的にどういう意味の違いがあるのかって、そういう話はここでしてない。抽象的な話をしている。当然ながら具体的な話を理解しておいたほうがいい。でも、理解してる俺が「こうでいいよ」って言ってんだからそれを聞いておきなさい。本当に理解したいならコンパイラ通した結果どうなるかを見比べればいい。コンパイルしてデコンパイルすればいい。

複雑な背景はあれど、書きっぷりの話だからそういうことだ。

おまえ「いや、あとでメソッド内にawaitすべきTaskが増えたらasyncメソッドにしなきゃいけないじゃん。」

うむ。一理あるような無いような。とはいえ一旦、async/await無しにしといたほうが好いとは思ってる。ひとつのメソッドの中で2回awaitしたいケースって、最初っからそうなってるイメージある。もともと単一のTaskで済んでたメソッドは、それはそれで完成形であるのでは。そのメソッドに対して追加で仕事を増やすのも勿論ひとつの解だけど、ホントにそうすべきかね。あるいは呼び元で済ませて引数でなんらか渡して解決するって道筋を考慮してみてもいいんじゃない。

◆awaitしろ

TaskをWaitすんな。Resultすんな。やめろ。

なぜか。デッドロックして無様に射精することがあるからってのもそうなんだけど、じゃあデッドロックしない場面ではResult書いてもいいのだろうか。

それは俺が許さん。一切のWaitとResultを禁ずる。書き方を一辺倒にするべきである。そう思うのはJavaScriptのPromise(とthencatch)にしてやられたから。Promiseで書かんでええもんを「まぁ間違ってはないし」の精神で許容し続けてたらもう可読性の落ちること風林火山の如しであることだよなぁと思い続けて早幾年たちました。

だからもうasync/await以外ゆるさん。Promise警察だ!

▼Optional Chainingとawait

nullableなインスタンスの持っているasyncなメソッド叩き、戻り値を受け取る場合。Resultを使いたくなったりする。

つまりこういうことよ。Mainだけ見れば理解できると思う。

public async Task Main()
{
    Hoge? hoge = null;
    var a = hoge?.Hogehoge().Result ?? 2;
}

public class Hoge
{
    public async Task<int> Hogehoge()
    {
        await Task.Delay(0);
        return 1;
    }
}

これな。確かに、以下の書き方だと怒られるんだわ。

var a = await hoge?.Hogehoge();

hogeがnullだと、nullをawaitしちゃうことになるから。「nullをawaitってなんじゃい。このエビフライ定食が。」って怒られる。でもResultは書いちゃ駄目。めっ💕めだぞっ💕

こう書け。

var a = await (hoge?.Hogehoge() ?? Task.FromResult(100));

hogeがnullだったらTask.FromResultをawaitしてくれる。この例だと100が入ってるから100がaに入るわけです。

Task<int>じゃなくてただのTaskだよってひとはTask.CompletedTaskを使いなさい。

◆全部のメソッドをAsyncにするべき?

いやいや。そんなわけねぇ。ネットワークまわりっていうか、ファイル操作だののI/O系をasyncにするだけでいい。I/Oって言ってんのは、画面の描画とかも含まれるよ。

「asyncにするべき」っていうかTaskにするべき場合はある。I/O絡まない処理を、まぁTaskにはしてもいいけどasyncにする理由は無い。

既にあるasyncメソッドをawaitしてる処理は、否も応もなく自身もasyncメソッドだろう。

I/Oまわりだけに絞ってasyncメソッドとしたときに、どういう副作用が発生するか。それはすなわち、叩くメソッドがI/Oに関わるかどうかが一目でわかるようになる。I/Oしねぇくせに無理やり作ったasyncなメソッドがあると、その判断つかなくなってくる。

「Async版とそうじゃない版どっちも作ればよくない?」っていう意見もあるけど、普通に作れば片方だけで事足りる。「Asyncしかない!困る!」とか言って変に詰んでるのは、それ設計っつーか見通しがおかしい。作る処理を想像した段階でI/Oするかどうかは判断できるはずだ。

◆ConfigureAwait(false)する?

書かない理由がなければ書くべき。書くべきだけど、単なるビジネスロジックなら書かんでもええねんダルいから。

なんらかのライブラリを提供する側になったら考え始めろ。

全部に書けばいい無敵構文ってワケでもにゃぁし、とりあえずは書かなくていいんだよ。許す。時に書くべきかもしれんが、書くメリットと書かないメリットを天秤にかけるべきだろうが。盲目的になるな。

◆結

歯医者についての記事書こうと思ってたら、なんでかしらんが技術記事書いてた。

あるある。あるよね~。