WEB+DB PRESS Vol.119

読み終わったので感想メモ。

特集1 フロントエンド脱レガシー

サイボウズの kintone を Closure Library から React + TypeScript に移行するのに、 テストを書いて既存の振る舞いを壊していないことを確認しつつ、着実に進めてやりきったのは見事。 欲を言えば、React + TypeScript ではどんなアーキテクチャを採用したのかも知りたかったな。

特集2 インフラ障害対応演習

本業でもパッケージから SaaS に移行しつつある。幸いなことに、大規模な障害にはまだ遭遇していないけど、どんな障害の可能性があって、どういった対策があるのか、インターネット上にはなかなか情報が無い。本特集を参考に障害対応のマニュアルを整備していきたい。ステージング環境を使っての訓練も、できれば取り組みたいところ。

特集3 深層学習入門以前

深層学習で現在実現されていることと、その基になっているアイデアを数式無しで解説し、 その先はチュートリアルに促すという割り切りっぷりが功を奏していると思う。 深層学習の「凄そうだけど良く分からない」感が払しょくされ、 深層学習で今のところどんなことが可能なのかをざっと把握できて良かった。

モダンフロントエンド技術 第3回 ユーティリティクラスベースのCSS設計

Tailwind CSS のような、ユーティリティベースの CSS フレームワークがなぜ必要か分かっていなかったけど、React や Vue のコンポーネントベースのフロントエンドと親和性が高いからか。なるほど。納得した。

アオアシ(22)

青森星蘭のモデルは当然青森山田で、北野蓮のモデルは多分柴崎なんだろうな。その北野蓮は葦人と同じ俯瞰の能力を持ち、技術は超高校級と、完全に葦人の上位互換的な存在。葦人自身がこいつにはなれないと自覚してしまうような相手に対して、葦人はどう戦うんだろう。阿久津と共闘しそうな感じも匂わせていて、次巻が楽しみ。

というか、勝てばプレミア制覇なわけだけど、そうなったらユースでやること無くなっちゃいそうな気が。残るはユース年代の日本代表くらい。でも、葦人的には代表よりもプロ優先だろうし。青森星蘭戦後の展開は予想が難しいな。気が早いか。

あと、葦人と花と杏里の三角関係はしばらく動きは無いだろうなと思っていたら、杏梨からまさかの燃料投入。そして葦人の鈍感主人公発動。花のときと大違いで、さすがに杏里が不憫だ。

波よ聞いてくれ(8)

8巻は北海道地震編の続き。麻藤が語った、災害時に「日常を演出しなきゃならない」ラジオの役割を、ミナレは見事に果たしてみせた。非日常が日常なミナレだからこそ、演出できた日常感だったんだろうな。次あるかどうかも分からない、ミナレの真っ当な活躍だっただけに、欲を言えばもっと長い尺で描いて欲しかった。

HttpClient を Moq でモック化する

厳密には、HttpClient が内部で最終的に呼び出す HttpMessageHandler を、Moq を使ってモック化する。

まずは HttpMessageHandler のインタフェースを、存在しないのででっち上げる。

public interface IHttpMessageHandler
{
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}

HttpMessageHandler.SendAsync は protected internal なので、一筋縄ではいかない。Moq が提供している protected なメンバーをモック化する機能を使う。

var handlerMock = new Mock<HttpMessageHandler>();
handlerMock.Protected()
    .As<IHttpMessageHandler>()
    .Setup(m => m.SendAsync(
        It.Is<HttpRequestMessage>(r =>
            r.RequestUri.PathAndQuery.Contains("/api/account/login") &&
            r.Method == HttpMethod.Post &&
            r.Content.ReadAsStringAsync().Result.Contains("foobar") &&
            r.Content.ReadAsStringAsync().Result.Contains("P@ssw0rd")
        ),
        It.IsAny<CancellationToken>()))
    .Returns(() =>
    {
        var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
        return Task.FromResult(response);
    });

ついでに IHttpClientFactory もモック化しておく。こちらは素直。

var factoryMock = new Mock<IHttpClientFactory>();
factoryMock.Setup(m => m.CreateClient(It.IsAny<string>()))
    .Returns(() =>
    {
        return new HttpClient(handlerMock.Object, false);
    });

HttpMessageHandler を継承したテスト用クラスを定義するのが簡単だけど、他は Moq 使っているのに、HttpClient 関連だけ違うっていうのは気持ち悪いので、なんとかやってみた。

とんかつ わか葉 別館

国体通り沿い、警固神社の斜め前に、『とんかつ わか葉 別館』がオープンしていたので行ってみた。別館はカツ丼専門店。本店のトンカツを以前食べたことあるけど、脂身すら甘くジューシーで美味だったと記憶している。期待大。

f:id:griefworker:20201022215116j:plain

一番人気の『カツ玉丼』の食券を購入。どれくらいのボリュームか分からなかったので、今回は並盛にしておいた。ご飯の上にふわとろ玉子、そしてその上にサクサクのトンカツが鎮座していた。とじていないカツ丼は初めて。東京のグルメ誌で見たことはあるけど、福岡でお目にかかれるとはなぁ。

f:id:griefworker:20201022215123j:plain

とじられていないのでトンカツは当然衣サクサク。それにオムレツのようにふわとろに仕上がった玉子、丼タレが染みたご飯と、三位一体。まごうことなきカツ丼。いや、期待以上だった。友楽が天神から消えてはや数年。カツ丼ロスが続いていたが、ついに満足のいく丼と出会えたかもしれない。カツ玉丼は本店でも食べることができるし、本店の方が行きやすいので、今度は本店の方も食べてみようかな。

関連ランキング:とんかつ | 西鉄福岡駅(天神)天神南駅天神駅

ARC005C - 器物損壊!高橋君

atcoder.jp

今までの幅優先探索ではスタート地点からの距離をカウントしていたけど、今回はその区画に着くまでに最低何回塀を壊す必要があるかをカウントしてみた。

using System;
using System.Collections.Generic;
using System.Linq;

namespace ARC005C
{
    class Program
    {
        static void Main(string[] args)
        {
            var HW = Console.ReadLine().Split(' ');
            var H = int.Parse(HW[0]);
            var W = int.Parse(HW[1]);
            (int x, int y) start = default;
            (int x, int y) goal = default;
            var map = new char[H, W];
            var broke = new int[H, W];
            for (var y = 0; y < H; y++)
            {
                var row = Console.ReadLine().ToList();
                for (var x = 0; x < row.Count; x++)
                {
                    map[y, x] = row[x];
                    broke[y, x] = int.MaxValue;
                    if (row[x] == 's')
                    {
                        start = (x, y);
                    }
                    else if (row[x] == 'g')
                    {
                        goal = (x, y);
                    }
                }
            }

            var queue = new Queue<(int x, int y, int c)>();
            queue.Enqueue((start.x, start.y, 0));
            broke[start.y, start.x] = 0;
            while (queue.Count > 0)
            {
                var current = queue.Dequeue();
                foreach (var next in GetNexts((current.x, current.y)))
                {
                    // マップの外だったらスキップ
                    if (next.x < 0 ||
                        next.y < 0 ||
                        next.x >= W ||
                        next.y >= H)
                    {
                        continue;
                    }

                    // 塀だった場合、壊した回数が2回未満なら、壊して探索続行
                    if (map[next.y, next.x] == '#' && current.c < 2)
                    {
                        broke[next.y, next.x] = Math.Min(
                            broke[next.y, next.x],
                            current.c + 1);
                        queue.Enqueue((next.x, next.y, current.c + 1));
                        continue;
                    }

                    // 塀以外だった場合で、まだ訪れていないか、
                    // 現在よりも多く壁を壊して訪れていたら、探索続行
                    if (map[next.y, next.x] != '#' && broke[next.y, next.x] > current.c)
                    {
                        broke[next.y, next.x] = Math.Min(
                            broke[next.y, next.x],
                            current.c);
                        queue.Enqueue((next.x, next.y, current.c));
                        continue;
                    }
                }
            }

            var answer = broke[goal.y, goal.x] <= 2 ? "YES" : "NO";
            Console.WriteLine(answer);
        }

        static IEnumerable<(int x, int y)> GetNexts((int x, int y) p)
        {
            yield return (p.x - 1, p.y);
            yield return (p.x + 1, p.y);
            yield return (p.x, p.y - 1);
            yield return (p.x, p.y + 1);
        }
    }
}

AGC033A - Darker and Darker

atcoder.jp

開始地点となる # の位置を記録しておいて、まとめて幅優先探索を行う。一つずつ幅優先探索を完了していったらタイムオーバーになるので、まとめてやるのが重要だった。

using System;
using System.Collections.Generic;
using System.Linq;

namespace AGC033A
{
    class Program
    {
        static void Main(string[] args)
        {
            var HW = Console.ReadLine().Split(' ');
            var H = int.Parse(HW[0]);
            var W = int.Parse(HW[1]);
            var A = new List<string>();
            var starts = new List<Point>();
            for (var y = 0; y < H; y++)
            {
                var row = Console.ReadLine();
                A.Add(row);
                for (var x = 0; x < row.Length; x++)
                {
                    if (row[x] == '#')
                    {
                        starts.Add(new Point(x, y));
                    }
                }
            }

            var dist = Enumerable.Range(0, H)
                .Select(x => Enumerable.Repeat(-1, W).ToList())
                .ToList();
            var queue = new Queue<Point>();
            foreach (var start in starts)
            {
                queue.Enqueue(start);
                dist[start.Y][start.X] = 0;
            }
            while (queue.Count > 0)
            {
                var current = queue.Dequeue();
                foreach (var next in current.GetNexts())
                {
                    if (next.X >= 0 &&
                        next.X < W &&
                        next.Y >= 0 &&
                        next.Y < H &&
                        A[next.Y][next.X] == '.' &&
                        dist[next.Y][next.X] == -1)
                    {
                        dist[next.Y][next.X] = dist[current.Y][current.X] + 1;
                        queue.Enqueue(next);
                    }
                }
            }

            var answer = dist.SelectMany(x => x).Max();
            Console.WriteLine(answer);
        }
    }

    readonly struct Point
    {
        public readonly int X;

        public readonly int Y;

        public Point(int x, int y) => (X, Y) = (x, y);

        public IEnumerable<Point> GetNexts()
        {
            yield return new Point(X - 1, Y);
            yield return new Point(X + 1, Y);
            yield return new Point(X, Y - 1);
            yield return new Point(X, Y + 1);
        }
    }
}