ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

「よくわかるHTTP/2の教科書」を読んだ

GWの最後の2日で「よくわかるHTTP/2の教科書」を読みました。
http://www.ric.co.jp/book/contents/book_1177.html

HTTP/2に限らず、HTTP周りについて非常に分かりやすく書かれていると感じました。

以下、ほぼ「分かりやすかった」の羅列になっていますが、備忘録替わりに各章ごとに注目した部分の感想を書いておきます。

感想

第2章

HTTP・TLSTCPという、「裏」で動いているプロトコルについて、非常に分かりやすかったです。

第3章

HTTPについて。初期バージョンは古く1990年代には使われていましたとあります。
余計な感想かもしれない、かつインターネットがメジャーになった年代を考えれば当然だが、工業製品の歴史と比べると新しいと感じました。

CookieのセッションIDの例や、KeepAliveについては分かりやすいと感じました。

第5章

HTTP/2を解説している、本書のメイン部分。
最初のHTTP/2の概要で、HTTP/2の独自機能が分かりやすく書かれています。
HTTP/2のストリームとフレーム、コネクションについて分かりやすかったです。

第6章

WebSocket、HTTP Live Streaming、Open ID Connectなどについて分かりやすく書かれています。
QUICについてはほぼ初見だったが、こちらも非常に分かりやすかったです。

まとめ

アプリケーション周りの実装を行っているだけだと、HTTP周りのプロトコルについては
フレームワークなどで隠蔽されており、詳細までは意識しないことが多いかと思います。
この辺りを改めて意識する or HTTP周りの知識を整理するのに、いい本だと思いました。

aws cdk でAthenaのNamed Queryを作成する

AWS Athenaにはよく使うクエリを保存する Named Queryという機能があります。マネージメントコンソール上では「Saved Queries」というタブに表示されます。

今回はこのNamed Queryをaws cdkでデプロイしてみました。aws cdkを使ったのは、クエリは特に開発時は何度も変更するため、cdkを使って繰り返しデプロイできるようにしたら開発効率が上がるのではないかと思ったためです。(もちろん画面にて更新することもできますが)

Named Query以外の、データベースやテーブルなどは、今回はマネージメントコンソールから手動で作成しました。以下、今回の作業の流れとなります。

  • S3のバケットを作成し、検索元データのCSVをアップロードする。
  • Athenaの画面にてデータベースを作成する。
  • Athenaの画面にてテーブルを作成する。
  • クエリ結果の保存先「Query result location」をAthenaの画面にて作成する。
  • Named Queryをaws cdkで作成、デプロイする。

以下、それぞれについて書いていきます。

データベース作成

最初のS3のバケット作成、データのCSVのアップロードについては省きます。データのCSVを事前にS3にアップロードしておきます。
データベースについては、Athenaの「Query Editor」に以下のcreate database文を流すことで作成しました。(今回はsample_carsというデータベース名とします。センス無いですが、テーブル名も同じにしました。)

create database sample_cars;

なおデータベースを作成しないと、以下のテーブル作成で使う「Create table」のリンクが出てきませんでいた(私の作業ミスの可能性もありますが。明示的にデータベースを選ばなければならなかったのか?)

テーブル作成

Athenaの画面の左側に「Create table」というリンクがあるので、クリックします。カラム名と型を指定できるので、CSVの中から取得したい対象のカラムを指定して、画面にてテーブルを作成します。画面に表示されるCreate文は以下で使うので保存しておきます。

画面にて作成されたデフォルトの状態だと、CSVの一行目が列名を示すヘッダーとなっている場合、ヘッダーが検索結果として抽出されてしまいます。これを防ぐ為には、画面にてテーブルを作成したときに表示されたCreate文を保存しておき、「'skip.header.line.count'='1'」をプロパティに追加して再度実行します。Create文は以下のようになります。

CREATE EXTERNAL TABLE IF NOT EXISTS sample_cars.sample_cars (
  `id` int,
  (中略)
)
(中略)
TBLPROPERTIES (
  'skip.header.line.count'='1', -- これを追加
  'has_encrypted_data'='false'
);

Query result location

クエリの実行結果の保存先を「Query result location」で指定します。画面右上の「Settings」をクリックするとダイアログが出てくるので、そこで指定します。

aws cdkでNamed Queryを作成

プロジェクト作成

さて本題のNamed Queryです。aws cdkでのプロジェクト作成は、以下の公式チュートリアルや、以前書いた記事を参考にしてください。
Getting Started With the AWS CDK - AWS Cloud Development Kit (AWS CDK)
aws-cdkを触ってみた - ソースコードから理解する技術-UnderSourceCode

aws cdkのプロジェクトフォルダを作成し、以下を実行します。

$ npx cdk init app --language=typescript
$ npx cdk --version

今回はaws cdkの「aws-athena」モジュールを使うので、以下のコマンドでインストールします。

$ npm install @aws-cdk/aws-athena

cdkのプロジェクトが作成されると、「lib」フォルダ内の.tsファイルに「The code that defines your stack goes here」というコメントがあります。ここに任意の作成したいコードを記述します。今回はAthenaのNamed Queryを作成するコードを記載します。.tsファイルは以下のようになりました。

import * as cdk from '@aws-cdk/core';
import athena = require('@aws-cdk/aws-athena');

export class CdkAthenaQueryStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    new athena.CfnNamedQuery(this, 'MySampleCars', {
      database: 'sample_cars',
      description: 'this is a sample named query.',
      name: 'sample-cars-query',
      queryString: 'select id, maker, model, transmission_type from sample_cars.sample_cars limit 10;',
    })
  }
}

「sample_cars」データベースを指定し、「queryString」にてSelect文を定義しているのが分かるかと思います。次にこれをデプロイしてみます。

Named Queryのデプロイ

デプロイについては標準的なaws cdkの手順通りです。私は以下のコマンドで実行しました。

$ npm run build
$ npx cdk synth
$ npx cdk deploy

デプロイを実行すると、Athenaの画面の「Saved Queries」タブに、クエリが出来ているはずです。選択して実行できることを確認してみてください。

次にクエリの変更についてです。cdkの「queryString」のSelect文を変更し、以下のコマンドを実行します。

$ npm run build
$ npx cdk synth
$ npx cdk diff
$ npx cdk deploy

最初の「npm run build」を忘れると変更が反映されないので注意です。「diff」を使うと変更箇所が分かります(今回はクエリになるはずです)。デプロイして、画面からクエリを実行して結果が変わっていれば成功です。

gofakeitでサンプルデータを作ってみる

GolangでサンプルデータのCSVを作成したかったので、いくつかライブラリを検索したところ、「gofakeit」というライブラリを見つけました。
GitHub - brianvoe/gofakeit: Random fake data generator written in go

READMEを見れば使い方は分かりますが、備忘録として私が作ったものも載せておきます。
やったことは以下になります。

  • 10件ほどのサンプルデータをCSV形式で作成
  • データはgofakeitを使ってランダムに作られるようにする
  • CSVの出力はgocsvを使う

以下、ソースと実行方法です。

インストール

$ go get -u github.com/gocarina/gocsv
$ go get github.com/brianvoe/gofakeit/v5

ソース

package main

import (
	"fmt"

	"github.com/brianvoe/gofakeit/v5"
	"github.com/gocarina/gocsv"
)

type Car struct {
	ID               int    `csv:"id"`
	Maker            string `csv:"maker"`
	Model            string `csv:"model"`
	TransmissionType string `csv:"transmission_type"`
}

func main() {
	gofakeit.Seed(0)

	cars := []*Car{}

	for i := 0; i < 10; i++ {
		car := &Car{
			gofakeit.Number(1, 100),
			gofakeit.CarMaker(),
			gofakeit.CarModel(),
			gofakeit.CarTransmissionType(),
		}
		cars = append(cars, car)
	}

	data, err := gocsv.MarshalString(&cars)
	if err != nil {
		panic(err)
	}
	fmt.Println(data)
}

実行

「go build」でビルドし、できたバイナリ名が「main」とします。

$ ./main # 出力されるデータを確認する
$ ./main > sample_cars.csv # パイプでcsvに出力する

「Code Craft エクセレントなコードを書くための実践的技法」を読んだ

「Code Craft エクセレントなコードを書くための実践的技法」を読みました。
https://www.amazon.co.jp/dp/B00P7R545M/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

私が読んだのはKindle版ではなく、紙の本ですが。。。
2007年に初版発行された本ですが、現在でも通用する名著だと思いました。
以下、読んだ時に気になったところを各章毎にメモ書き形式で挙げておきます。読み進めると納得できるような記述が(以下に挙げること以外にも)次々と出てくるところが名著である由縁だと思いました。

第1章 守りを固める

「防衛的プログラミング」について。防衛的プログラミングではないことについて、エラーチェックや(ユニット)テストを挙げている。
本書でも触れられているが、アサーションによるエラーチェックや、厳しいユニットテストが防衛的プログラミングだと勘違いすることが多い(私もそうだった)。
詳細にはここでは書かないが、本書でいう「防衛的プログラミング」はコーディングスタイル、適切なデータ構造、カプセル化コンパイルエラーを全てONにするなどといった安全にプログラムを書く基本を守ったプログラミングのことである。

第2章 見事に描かれた設計図、第3章 名前の意義

この辺りは最近だとフォーマッタの適用や、「名前重要」という言葉などで言われていることだと思う。
勿論、フォーマットの形式を決めることなど、プログラミングにおいて意識しておく必要があることではある。

第4章 必要な情報を余さず書く

関数から早めにreturnする文を書くこと、安易な「最適化」は読みやすさを損なうこと、適切な名前を使用することについて書かれている。
「コードをアトミックな関数に分解する」の項目は、こちらも参考になると思う。
https://speakerdeck.com/sonatard/coheision-coupling

第5章 的確なコメント

コメントについて扱っている章で「この節も読み飛ばさないでください!」って書いてあった(笑)。コメントを軽視しがちなウチらの心理を見透かしてるなと思った。(実際に自分は読み飛ばしそうになった)。

第7章 プログラマーの道具箱

「適度な大きさのプロジェクトの作業を、これまでと違った環境で」やってみることを勧めている。
言語について。言語は「それ自体がツール」だとハッキリ書いてある。(エディタとかのツールを説明してきた流れで)。そして数種の言語を学ぶことを書いている。まあこの辺りは、一つだけの言語で済む仕事は少ないはず…。

第8章 テスト

「コードを書きながら」テストすることを訴えている。いわゆる「テストファースト」にも触れているが、絶対視はしていないようだ。このあたりは自分も同意見。

第9章 誤りの検出

本章より巻末の方が興味深い。デバッガを使うべきタイミング、デバッガで無闇やたらとソース内を動くのは危険というのには納得。
デバッガを使う頻度について、年中は多すぎ、使わないのは少なすぎとある。

第11章 速さを追い求める

パフォーマンスについては、開発の開始時点から考えるべきだと書いてある。これについては同意だが誤解していた。。。
とはいえ、「コードの最適化は必ず代償を伴うもの」とも書いてある。

第12章 不安の固まり

この章はセキュリティに関してだったが、今のアプリをクラウド上で実行する仕事だと、クラウドでのインフラ設定でセキュリティを確保することが多いと思う。勿論、SQLインジェクション対策などプログラムでのセキュリティホールは作らない前提とはなるが。

最初の方の章になるが、読みやすいコードを書く上での適切な関数分割やコメントなどについて言及しているところがあった。高級言語によるプログラムでの考え方は分かったが、yamlなど設定ファイルでの読みやすさの担保の方法も知りたい。

第13章 グランドデザイン

プログラミングは設計作業であると書いてある。ただし「テキストエディタは書くべき内容を練る場所ではない」とも書いてある。

第14章 ソフトウェアアーキテクチャ

システムアーキテクチャは関係者全員が見れる所にドキュメントとして記述することとある。全体像が一枚絵になっていないシステムって多いんだよなとか思っていたら、その後に優れたアーキテクチャは「一片のエレガントな図に要約できるかどうかが鍵」って書いてあった。

第15章 進化か革命か?

保守によるソースの修正で「老朽化」することを指摘している。「警告のサイン」の項であげられる事は、あるある過ぎると思った。
コードが混乱する理由は「複雑だから」とある。この章の最後に保守について、良くないコードを扱う覚悟と、書いた人の「メンタルモデル」を理解することが書かれている。

第17章 チームの力

チームワークは優れた開発者に不可欠であるとハッキリ書いてある。
大切だと思うので、いくつか抜粋する。
「ハイクオリティなプログラマーになるためには、ハイクオリティなチームプレイヤーにならなくてはなりません。」
「そして、有益なチームメンバーとなるためには、技術以外のさまざまなスキル、特質、姿勢をまずは伸ばさなくてはなりません。」
プログラミング言語に対する技能や設計能力について考えるのは、その後です。」

コードの特定部分の「所有者」のように振る舞うことの弊害についても書いてある。この「チームの力」の章は、大切な章だと思った。

最後に一つ抜粋。良いプログラマーについては「ソフトウェアシステムの向上に役立つのであれば、どんな種類の開発作業でもこなす」。

第19章 仕様の具体化

仕様の具体化について。要求仕様書の内容について「離散要求」「非離散要求」に分かれると書いてある。これ、いわゆる「機能要件」「非機能要件」のことだと思う。

仕様書については「本当に必要なドキュメントのみ」書くべきだと言っている。これには同意。システムの全体像、インフラ構成など、ソースから読めない情報がドキュメント化されていないのは困る(大抵、有識者を探して聞くことになる)。

仕様書を「誰も読んでくれない」から書かないのではなく、「『あなたの』脳を活性化」するために書くべきと言っている。また仕様書は後々まで残り、後任の役にも立つだろうと。。

「仕様書を書く時間がなければ、まともなコードを書く時間もないと思え」ともあった。

第20章 美しき獲物たちか?

レビューについての章。「『批判に晒されていない人は、大したことをしていないのだろう』 - Donald H. Rumsfeld」との記述が、章の最初にある。

第21章 完了日は未定

見積もりについては、例外なく経験からの推測に過ぎないと、まず最初に書いてある。見積もりの精度を上げる方法として、作業内容をできるだけ小さい単位に分割する、分割した単位ごとに人時 or 人日の見積もりをする、単位ごとに見積もりを合算する、とある。

この章の最後に、気を散らすものとしてメールや電話を挙げ、断ることを学ぶとあるが、一方で別の章ではコミュニケーションの大切さも説いていた。今だとチャットとかだろうけど、この辺は万能な対応策は無いんだろうと思う。

スケジュールに対して前向きで楽観的であり続けることも大切だと書いてあるが、、、ここもやはり万能な策が無いが故の精神論になるのだと思う。

見積もりについては以前読んだ「ソフトウェア見積り 人月の暗黙知を解き明かす」も大変参考になった。
https://www.amazon.co.jp/ebook/dp/B00KR96M6K

第24章 次の行き先は?

最後の章。コーディング能力を高める方法は「習うより慣れろ」しかないと書いてある。


どの章も大切なことが沢山書かれていましたが、中でも1章と17章は特に重要だと思いました。

nodeのプロジェクトにESLintを導入してみる

undersourcecode.hatenablog.com

以前こんなのを書いていたが、さすがに2年以上前だと古くなっているので書き直してみます。
とはいっても、今回はLambdaは関係ないし、ESLint公式のGetting Startedそのままです。
まあ自分宛の備忘録ってことで。

ESLint Getting Started
Getting Started with ESLint - ESLint - Pluggable JavaScript linter

プロジェクト作成

node、npmは入っている前提で。

$ npm install eslint --save-dev
$ npm init
$ npx eslint --init
? How would you like to use ESLint? To check syntax and find problems
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? None of these
? Does your project use TypeScript? No
? Where does your code run? Node
? What format do you want your config file to be in? JavaScript
・・・

今回は画面がないJavaScriptファイルだけのプロジェクトだが、画面があるときなどは「$ npx eslint --init」の選択肢は変わってくるでしょう。

.eslintrc.js

ESLintの設定項目の一覧は以下を参照。
List of available rules - ESLint - Pluggable JavaScript linter

取り敢えずrules欄に2つだけ追加してみました。

    "rules": {
        "semi": ["error", "always"],
        "quotes": ["error", "double"],
    }

実行コマンド

以下のコマンドでESListのチェックと、main.jsの実行をします。

$ npx eslint ./*.js
$ node main.js

Gorillaのcontextとmuxを触ってみる

Gorilla, the golang web toolkit
GorillaというGolangのWeb向けツールキットについて調べ始めました。
Webアプリ用に色々なものが用意されているのですが、フレームワークではなく、あくまで必要なものを自分で取捨選択してつかう「ツールキット」です。

今回はこのGorillaの中から、contextとmuxを触ってみました。以下、公式のサンプルを参考に自分が書いてみたソースのメモ書きです。

context

context - Gorilla, the golang web toolkit

contextはリクエストの有効期間中、値を保存しておく機能です。何のこっちゃという気もしますが、ソースを見た方が手っ取り早いかと思います。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

const key1 Key = "key1"
const key2 Key = "key2"

// Key reprisents context key.
type Key string

// GetContext returns a value for this package from the request values.
func GetContext(r *http.Request, key Key) string {
	if rv := context.Get(r, key); rv != nil {
		return rv.(string)
	}
	return ""
}

// SetContext sets a value for this package in the request values.
func SetContext(r *http.Request, key Key, val string) {
	context.Set(r, key, val)
}

func contextHandler(w http.ResponseWriter, r *http.Request) {
	urlValue := r.URL.Path[1:]

	SetContext(r, key1, urlValue)
	SetContext(r, key2, time.Now().Format("2006/01/02 15:04:05"))
	// do something...
	value1 := GetContext(r, key1)
	value2 := GetContext(r, key2)

	fmt.Fprintf(w, "get key1 = %s\n", value1)
	fmt.Fprintf(w, "get key2 = %s\n", value2)
}

func main() {
	http.HandleFunc("/context/", contextHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

contextHandler内で、「http://localhost:8080/context/sample」のようなURLから「context/sample」の部分を抜き出し、キーを「key1」としてcontextに保存しています。
また現在日時を「key2」に保存しています。

「// do something...」で何らかの処理を行ったと仮定し、その後でcontextより値を取得して出力しています。

上記をビルドして実行し、curlを叩いてみると以下のように出力されます。(現在日時の部分は異なってきますが・・・)

$ curl http://localhost:8080/context/sample
get key1 = context/sample
get key2 = 2020/03/11 21:54:08

contextに設定した値が、無事取得できているようです。

mux

mux - Gorilla, the golang web toolkit

muxはザックリと書くと、URLのルーティング周りの機能を提供しています。
上記の公式が分かりやすいのですが、

  • URLパラメータを含むURLのルーティング
    • URLパラメータは正規表現で指定できる
    • サブルーティングでグループ化も可能
  • 静的ページの格納先パスを指定
  • middlewareによる共通処理の実装

などです。

これらのサンプルを書いてみました。

標準的な使い方

先に書いた

  • URLパラメータを含むURLのルーティング
    • URLパラメータは正規表現で指定できる
    • サブルーティングでグループ化も可能

を実装しています。

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func productHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	key := vars["key"]

	fmt.Fprintf(w, "get key = %s\n", key)
}

func articlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	category := vars["category"]
	sort := vars["sort"]

	fmt.Fprintf(w, "get category = %s, sort = %s\n", category, sort)
}

func articleHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	fmt.Fprintf(w, "get id = %s\n", id)
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/products/{key}", productHandler).Methods("GET")

	s := r.PathPrefix("/articles").Subrouter()
	s.HandleFunc("/{category}/{sort:(?:asc|desc|new)}", articlesCategoryHandler).Methods("GET")
	s.HandleFunc("/{category}/{id:[0-9]+}", articleHandler).Methods("GET")

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

「/products/」はURLパラメータを、「/category/」はURLパラメータの正規表現での定義、およびサブルーディングでグループ化してURLパラメータが「asc」「desc」「new」の場合と数値の場合とで別々のハンドラを指定しています。

ビルドしてcurlで実行すると以下のようになります。

$ curl http://localhost:8080/products/xxx
get key = xxx
$ curl http://localhost:8080/articles/ccc/asc
get category = ccc, sort = asc
$ curl http://localhost:8080/articles/ccc/10
get id = 10

静的ページ

cssや画像ファイルなど静的ページを格納することがあるかと思います。その静的ページの格納先の例となります。
今回は「static」というフォルダ内の「hello.html」という静的なHTMLを用意し、それを呼び出すサンプルを書いてみました。

package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

const staticDir = "/static/"

func main() {
	r := mux.NewRouter()

	r.PathPrefix(staticDir).
		Handler(http.StripPrefix(staticDir, http.FileServer(http.Dir("."+staticDir))))

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

実行してcurlでこんな感じで呼び出すと、hello.htmlの内容が返却されるはずです。

$ curl http://localhost:8080/static/hello.html

middleware

各ハンドラに共通の処理を実装したい場合、middlewareという機能を使い、その中に共通処理を実装する方法があります。basic認証を共通処理として実装するサンプルを作ってみました。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

// User ...
type User struct {
	Name     string
	Password string
}

// BasicAuthMiddleware ...
type BasicAuthMiddleware struct {
	Users []User
}

// NewBasicAuthMiddleware ...
func NewBasicAuthMiddleware() BasicAuthMiddleware {
	user1 := User{"test", "pass"}
	user2 := User{"hello", "world"}
	users := []User{user1, user2}
	return BasicAuthMiddleware{users}
}

// Authenticate ...
func (mwr *BasicAuthMiddleware) Authenticate(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		username, password, _ := r.BasicAuth()
		log.Println("Middleware Authenticated.")

		// Authenticate logic
		exist := false
		for _, u := range mwr.Users {
			if u.Name == username && u.Password == password {
				exist = true
				break
			}
		}
		if exist {
			next.ServeHTTP(w, r)
		} else {
			http.Error(w, "Forbidden", http.StatusForbidden)
		}

	})
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	timeStr := time.Now().Format("2006/01/02 15:04:05")
	fmt.Fprintf(w, "Hello, now is %s.\n", timeStr)
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/hello", helloHandler)

	mwr := NewBasicAuthMiddleware()
	r.Use(mwr.Authenticate)

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

パスワードを平文 + べた書きで保持していますが(笑)、サンプルなので適当にやっています。本来であればデータベースなどにハッシュ化して保持するべきでしょう。。。

「helloHandler」というハンドラに対して、BasicAuthMiddleware 構造体の「Authenticate」メソッドを共通処理として事前に実行するようにしています。

実行してcurlを叩くと、ユーザとパスワードが正しいかを判定していることが分かるかと思います。

$ username=test
$ password=pass
$ curl -i --basic -u $username:$password http://localhost:8080/hello

ざっと書いたサンプルを上げてみましたが、以上です。

「TypeScriptネットワークプログラミング」を読んだ

TypeScriptに興味が出てきたのと、タイトルが気になったので
HTML5/WebSocket/WebRTCによる TypeScriptネットワークプログラミング」を読んでみました。

CUTT System:TypeScriptネットワークプログラミング

物凄く短いですが、読書記録代わりの感想などを書いてみます。

感想

「TypeScriptは何をしてくれるのか?」という項目があり、TypeScriptのメリットなどが書かれています。
TypeScriptについて解説したあと、解説した機能を使ったプログラムのサンプルが記載されているところが分かりやすかったです。

またチャットやSkype風のビデオ通話など身近なものを題材にしている点も、ネットワークプログラミングの理解を助けてくれると感じました。
WebRTCやP2Pなどネットワークに関する概要説明もあり、入門にはいいと思いました。

ただif文やfor文などの説明や、データベースのInsert文も出てくることなどから、全くの初心者というよりは、何らかのプログラムを組んだことがある人がTypeScriptやネットワークプログラミングに入門するときに読む本なのかなとも思いました。

小並な感想ですが、以上です。