17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

マイナビAdvent Calendar 2019

Day 19

未経験者が入社1カ月間で必死に読んだ課題図書6冊

Last updated at Posted at 2019-12-18

今年の11月1日に、未経験でマイナビのサーバサイドエンジニアとして中途入社しました。
最初の1ヶ月間は、上司から課題図書を指定してもらい、それをひたすらインプットしていたので、そのまとめをここに記したいと思います。
尚、私の所属しているチームではRuby on Railsを使用しています。

1冊目:「アジャイルソフトウェア開発の奥義」

Amazon
(https://www.amazon.co.jp/dp/4797347783/ref=cm_sw_r_tw_dp_U_x_4.I5DbA81G4XN)

内容

単一責任の原則

  • クラスを変更する理由は一つ以上存在してはならない。クラスには1つの責務のみを負わせる。2つ以上の変更理由があるということは、そのクラスは2つ以上の責務を持っていることと同義である。

オープン・クローズドの原則

  • ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対して開いて(Open)いて、修正に対して閉じて(Closed)いなければならない。
    • 拡張に対して開かれている 
      =モジュールの振る舞いを拡張できるということ。アプリケーションの仕様要求が変更されても、モジュールに新たな振る舞いを追加することでその変更に対処できる。
    • 修正に対して閉じている
      =モジュールの振る舞いを変更しても、そのモジュールのソースコードやバイナリコードはまったく影響を受けない。
    • コードの書き方
      • 既存のコードを変更するのではなく、新しいコードを追加することで変更に対応できる様にする。
      • 最初は変更が起きないことを前提にコードを書いておく。実際に変更が起きたら、今後それと同じような種類の変更があった場合に備えて抽象を導入する。

リスコフの置換原則

  • 派生型はその基本型と置換可能でなければならない。
  • 「IS-Aの関係」及び「振る舞い」の同等性の2つの条件を満たす必要がある。

依存関係逆転の原則

  • 上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである。
    「連絡してくるなよ、必要なときはこちらから連絡するから」(ハリウッドの原則)
  • 「抽象」は実装の詳細に依存ならない。実装の詳細が「抽象」に依存すべきである。

インタフェース分離の原則

  • 全てのインタフェースを一つのクラスに押し込めてしまうのではなく、 関連性を持ったインタフェースはグループ化し、抽象基本クラスとして分けて利用する。クライアントに、クライアントが使用しないメソッドへの依存を強制してはならない。

感想

  • SOLIDの原則について初めて勉強する機会になった。初学者は必ず目を通しておくべきだと思う。
  • テスト自体が有益なドキュメントになるため、テストファーストで書くべきであることや、ペアプロによってチームに専門知識が広まっていき、かつ作業効率が落ちることはなく、かえって欠陥率が著しく低下するということ、リファクタリングは1時間から30分おき程度に行うことなど、勉強になることが多く記載されていた。
    特に、「プログラミングの腕は平均的でも、他人とうまくやっていける人の方が、人とまとめに意思疎通できないスーパースターの集団よりもプロジェクトを成功させやすい」という記載が印象的だった。周囲とのコミュニケーションは引き続き大切にしていきたい。

2冊目:Head First デザインパターン

HeadFirst

Head First デザインパターン

内容

  • Strategyパターン
    一連のアルゴリズムを定義し、それぞれをカプセル化してそれらを交換可能にする。

  • Observerパターン
    オブジェクト間の1対多の依存関係を定義し、あるオブジェクトの状態が変化すると、
    それに依存しているすべてのオブジェクトが自動的に通知され更新されるようにする。

  • Decoratorパターン
    オブジェクトに付加的な責務を動的に付与する。デコレータはサブクラス化の代替となる、
    柔軟な機能拡張手段を提供する。

  • Factory Methodパターン
    オブジェクト作成のためのインタフェースを定義するが、どのクラスをインスタンス化する
    かについてはサブクラスに決定させる。Factory Methodにより、クラスはサブクラスにインスタンス化
    を先送りできる。

    • ファクトリの利点:

      すべての作成コードを一つのオブジェクトやメソッドに配置することで、
      コードの重複を避け、保守を一か所に集約する。これは、クライアントがオブジェクトの
      インスタンス化に必要な具象クラスではなく、インタフェースだけに依存することも意味する。
      これによってコードはより柔軟になり、将来拡張できるようになる。
  • Abstract Factoryパターン
    具象クラスを指定することなく、一連の関連オブジェクトや依存オブジェクトを作成するための
    インタフェースを提供する。

  • Singletonパターン
    クラスがインスタンスを一つしか持たないことを保証し、そのインスタンスをアクセスする
    グローバルポイントを提供する。

  • Commandパターン
    リクエストをオブジェクトとしてカプセル化し、その結果、他のオブジェクトを異なるリクエスト、
    キュー、またはログリクエストでパラメータ化でき、アンドゥ可能な操作もサポートする。

  • Adaptorパターン
    クラスのインタフェースをクライアントが期待する別のインタフェースに変換する。アダプタは
    互換性のないインタフェースのためにそのままでは連携できないクラスを連携させる。

  • Facadeパターン
    サブシステムの一連のインタフェースに対する統合されたインタフェースを提供する。
    ファサードは、サブシステムをより使いやすくする高水準インタフェースを定義する。

  • Templateパターン
    メソッドにおけるアルゴリズムの骨組みを定義し、いくつかの手順をサブクラスに先送りする。
    Template Methodは、アルゴリズムの構造を変えることなく、アルゴリズムのある手順をサブクラスに
    再定義させる。

  • Iteratorパターン
    内部表現を公開することなくアグリゲートオブジェクトの要素に順次アクセスする方法を提供する。

  • Compositeパターン
    部分ー全体階層を表現するために、オブジェクトをツリー構造に構成できる。Compositeパターン
    により、クライアントは個別のオブジェクトとオブジェクトのコンポジションを同じように扱うことができる。

  • Stateパターン
    オブジェクトの内部状態が変化した際にオブジェクトがその振る舞いを変更できる。オブジェクトは
    そのクラスを変更したように見える。

  • Compoundパターン
    2つ以上のパターンを組み合わせ、繰り返し発生する汎用的な問題を解決する解決策となる。

感想

  • デザインパターンについて、Metaphorや図を用いて分かりやすく説明してあり、大変勉強になった。
  • 何か建物を建てる際、闇雲に建てては、後程修繕などの変更が生じる際に困難を極める。
    それと同様に、Webサイトを作成する際にも、今後の修繕が避けて通れない性質上、
    闇雲にプログラムを書くのではなく、しっかり構成を設計してから書く必要がある
    ということを初めて学び、目から鱗の体験だった。
    抽象と具体を分離して、抽象に依存するように設計したり、オブジェクト間のやりとりを
    なるべく減らし、修正を小規模にとどめられる様に設計することの大切さをこの本を通して確り学べたと思う。

3冊目:オブジェクト指向設計ガイド

オブジェクト指向

オブジェクト指向設計ガイド

内容

  • オブジェクト指向設計とは
    「依存関係を管理すること」。オブジェクトが変更を許容できるようなかたちで依存関係を構成するための、コーディングテクニックが集まったもの。

  • メソッドをグループに分けクラスにまとめる

  • 変更が簡単になるようにコードを組成する

  • 単一責任を持つクラスを作る

  • データではなく、振る舞いに依存する。(Don't Repeat Your Self(DRY))

    • インスタンス変数はattr_readerで包み、直接参照しない。
class Gear
    def initialize(chainring, cog)
        @chainring = chainring
        @cog = cog
    end
end

上記のコードのままだと、下記の様に直接参照を招いてしまう。
変数は定義されているクラスからも隠蔽するべきである。

class Gear
    attr_reader :chainring, :cog
    def initialize(chainring, cog)
        @chainring = chainring
        @cog = cog
    end

    def ratio
    @chainring/@cog.to_f # <-- 破滅への道!!
    end
end

従って、カプセル化用のメソッドを作る方法としてattr_readerを使う。

class Gear
    attr_reader :chainring, :cog
    def initialize(chainring, cog)
        @chainring = chainring
        @cog = cog
    end
end

attr_readerを使うことで、実質以下の通り定義したこととなる。

def cog
    @cog
end

故に、インスタンス変数を修正する必要が生じた際には、独自のメソッドを実装すればよくなる。

class Gear
    attr_reader :chainring, :cog
    def initialize(chainring, cog)
        @chainring = chainring
        @cog = cog
    end
end

def cog
    @cog * 100 # <-- メソッドを定義し直している
end

なるほど!賢い!

  • データ構造の隠蔽

複雑なデータ構造への依存も避ける。
例:

class Obscuring References
   attr_reader :data
   def uinitialize(data)
       @data = data
   end
   
   def diameters # <-- 配列の構造に依存している!配列の構造が変わったら要変更。
       data.collect{|cell|
           cell[0]+(cell[1]*2)} # <-- リムは[0]にあって、タイヤは[1]にある。分かりにくい。
   end
end

尚、@dataの内容は以下の通りとする

@data = [[622,20],[622,23],[599,30],[599,40]]

diametersメソッドが配列の構造に依存しており、配列の構造が変わったら変更を余儀なくされる。また、cell[0]やcell[1]がそれぞれrim, tireを指しており、修正漏れにつながる。

従って、以下の様に、配列の構造に関する知識はメソッドに分離させる。

class RevealingReferences
    attr_reader :wheels
    def uinitialize(data)
        @wheels = wheelify(data)
    end
    
    def diameters #<-- 配列構造について何も知らない状態になった!
        wheels.collect{|wheel|
            wheel.rim + (wheel.tire*2)} # <-- cell[0]からwheel.rimへ、cell[1]からwheel.tireへ
    end
    
    Wheel = Struct.new(:rim, :tire) #<-- Arrayの配列をStructの配列に変換。明示的にクラスを書くことなく、いくつもの属性を一か所に束ねるための便利な方法。
    def wheelify(data) # <== 渡されてくる配列の構造に関する知識はwheelifyメソッド内のみに隔離。入力が変わってもここだけ変更すればよい。
        data.collect{|cell|
            Wheel.new(cell[0], cell[1])}
    end
end

こうすることで外部データの構造の変化に強くなる上、可読性も高まる。

依存関係を管理。疎結合 is 美徳。

class Gear 
    attr_reader :chainring, :cog, :rim, :tire
    def initialize(chainring, cog, rim, tire) # <-- 引数の順番を指定してしまっている
        @chainring = chainring
        @cog = cog
        @rim = rim
        @tire = tire
    end
    
    def gear_inches # <-- Wheelの名前に変更があったら、gear_inchesメソッドも変更しなければならない
        ratio * Wheel.new(rim, tire).diameter # <-- 「Wheelという名前のクラスが存在+Wheel.newにrimとtireが必要+Wheelのインスタンスがdiameterに応答+Wheel.newの最初の引数がrimで、2番目がtire」ということを知っている
    end
    
    def ratio
        chainring / cog.to_f
    end
end

class Wheel
    attr_reader :rim, :tire
    def initialize(rim, tire)
        @rim = rim
        @tire = tire
    end
    
    def diameter
        rim + (tire * 2)
    end
end

Gear.new(52,11,26,1.5).gear_inches 

引数の順番を指定してしまっている→ハッシュにする
Wheelクラスについて、Gearクラスはあまりに知りすぎている。
(「Wheelという名前のクラスが存在
+Wheel.newにrimとtireが必要
+Wheelのインスタンスがdiameterに応答
+Wheel.newの最初の引数がrimで、2番目がtire」
ということを知っている)

従って、次の様に書きかえる。

class Gear 
    attr_reader :chainring, :cog, :wheel
    def initialize(args) # <-- 「argsを使うことで固定された順番の引数」への依存を回避
        @chainring = args[:chainring]
        @cog = args[:cog]
        @wheel = args[:wheel] 
    end
    
    def gear_inches
        ratio * wheel.diameter # <-- @wheelはdiameterに応答するオブジェクトを保持している
    end
    
     def ratio
        chainring / cog.to_f
    end
end

class Wheel
    attr_reader :rim, :tire
    def initialize(rim, tire)
        @rim = rim
        @tire = tire
    end
    
    def diameter
        rim + (tire * 2)
    end
end

Gear.new(  # <-- ハッシュを使う様にして引数の順番への依存がなくなったが、キー名に依存。だが、より安定している。引数に関する明示的なドキュメントにもなっている。
    :chainring => 52,
    :cog => 11,
    :wheel => Wheel.new(26, 1.5)).gear_inches

ダックタイピング

  • もしオブジェクトがduckの様に鳴き、duckの様に歩くならば、 そのクラスが何であれ、それはduckである。(=渡された引数が何であれ、同様のメソッドを持っていれば、同じものとして扱い、中身の処理は関知しない)

↓Wikipediaの例

def test(foo) # <-- 引数fooがどのようなオブジェクトであれ、soundメソッドを呼び出せれば良いと割り切るのがduck typing
    puts foo.sound
end

class Duck
    def sound
        'quack'
    end
end

class Cat
    def sound
        'myaa'
    end
end

test(Duck.new) # --> quack
test(Cat.new) # --> myaa

↓悪い例

class Trip # <-- Tripが具象クラスとメソッドをいくつも知りすぎている
    attr_reader :bicycles, :customers, :vehicle
    
    def prepare(prepares) 
        preparers.each{|preparer|
            case preparer #<--クラスで分岐するcase文はダックで置き換え可能
            when Mechanic
                preparer.prepare_bicycles(bicycle)
            when TripCoordinator
                preparer.buy_food(customers)
            when Driver
                preparer.gas_up(vehicle)
                preparer.fill_water_tank(vehicle)
            end
        }
    end
end

class TripCoordinator
    def buy_food(customers)
        #...
    end
end

class Driver
    def gas_up(vehicle)
        #...
    end
    
    def fill_water_tank(vehicle)
        #...
    end
end

Tripクラスがいくつもの具象クラスとメソッドを知りすぎている状態から、以下の通り変更させる。

↓良い例

class Trip
    attr_reader :bicycles, :customers, :vehicle
    
    def prepare(prepares) # このprepareメソッドは、新しいPreparerを受け入れる際に変更が強制されることがない。
        preparers.each{|preparer|
        preparer.prepare_trip(self)}
    end
end
# 準備するメソッド(prepare)は、その引数がtripを準備することを望むので、prepare_tripはPreparerダックのパブリックインタフェースに含まれるメソッドになる。

class Mechanic # <--必要に応じて追加のPreparerをつくるのも簡単。
    def prepare_trip(trip)
        trip.bicycles.each{|bicycle|
            prepare_bicycle(bicycle)}
    end
end

class TripCoordinator
    def prepare_trip(trip)
        buy_food(trip.customers)
    end
end

class Driver
    def prepare_trip(trip)
        vehicle = trip.vehicle
        gas_up(vehicle)
        fill_water_tank(vehicle)
    end
end

  • ポリモーフィズム
    • 多岐にわたるオブジェクトが、同じメッセージに応答できる能力。メッセージの送り手は、受け手のクラスを気にする必要がなく、受け手は、それぞれが独自かした振る舞いを提供する。故に、一つのメッセージが多くの(poly)形態(morphs)を持つ。

継承によって振舞いを獲得する

  • 継承とは「メッセージの自動委譲」の仕組み

柔軟なインタフェースを作る

モジュールでロールの振る舞いを共有する。

  • ロールが共有するコードをモジュールに定義する

など。

感想

  • 本書はオブジェクト指向設計のRubyバージョンの本であり、今までJavaの本で理解に苦しんでいた自分にとって非常に分かりやすい良本だった。

4冊目: 「Webを支える技術」

alt
Webを支える技術

学んだこと

  • API
    • プログラム向けのインターフェース⇔人間向けのユーザインタフェース。
    • データフォーマットにXMLとJSONなどのプログラムが解釈しやすいものを使う。
  • WEBを支える最も大きな技術
    • HTTP、URI、HTML
    • URIを使って世界中のあらゆる情報を指し示し、HTMLでそれらの情報を文書フォーマットで表現し、HTMLというプロトコルを使ってそれらの情報を取得したり発注したりする。
  • ハイパーメディア
    • テキストや画像、音声、映像など様々なメディアを結び付けて構成したシステム
    • 非線形的にユーザが自分でリンクを設定して情報を取得する。⇔書籍は線形的に先頭から順に読む
  • 分散システム
    • 複数のコンピュータを組み合わせて処理を分散させる形式。⇔1つの中央コンピュータがすべてを処理する形式(集中システム)

Webの技術的なバックグラウンドとアーキテクチャ

  • REST(Representational State Transfer)
    • HTTPはもともとハイパーテキストを転送するためのプロトコルだったが、実際にはハイパーテキスト以外にも色々転送している。= 「リソースの状態」の「表現」
    • Webのアーキテクチャスタイル
  • 様々なハイパーメディアの誕生
    • 最初はHTMLだけ
    • HTMLに様々な意味を持たせることのできる技術としてmicroformatsが登場。
    • Webページの新着情報をサーバで配信し、専用のプログラムでそれをチェックするための用途にRSSが提案。→複数のバージョンが乱立したため、ATOMが採択された。
    • HTMLやATOMはXMLをベースにした構造化文書のためのマークアップ言語なので、データを記述するためには表記が冗長→単純なデータフォーマットのJSONが採用
  • リソース
    • Web上の情報のこと。サーバとクライアントの間でやりとりするデータのことを「リソースの表現」と呼ぶ。

RESTアーキテクチャスタイル

  • クライアント/サーバのアーキテクチャスタイル
    • クライアントはサーバにリクエストを送り、サーバはそれに対してレスポンスを返す。 ⇒クライアントとサーバに分離して処理できる。サーバを複数組み合わせて冗長化させれば、可用性を上げられる。
  • ステートレスサーバ「クライアント/ステートレスサーバ」
    • クライアントのアプリケーション状態をサーバで管理しないこと
    • 利点:サーバ側の実装を簡略化可能で、クライアントからのリクエストに応えた後、すぐに計算機リソースを開放可。
    • ステートレスに反する例:Cookie
  • キャッシュ「クライアント/キャッシュ/ステートレスサーバ」
    • リソースの鮮度に基づいて一度取得したリソースをクライアント側で使いまわす方式
    • 利点:サーバとクライアントの間の通信を減らすことでネットワーク帯域の利用や処理時間を縮小し、より効率的に処理できること。
  • 統一インタフェース「統一/クライアント/キャッシュ/ステートレスサーバ
    • URIで指し示したリソースに対する操作を統一した限定的なインタフェースで行うアーキテクチャスタイル
  • 階層化システム
    • 利点:
      ①インターフェースの柔軟性に制限を加えることで全体のアーキテクチャがシンプルになる。
      ②システム全体が階層化しやすい。
      サーバとクライアントの間にロードバランサを世知して負荷分散をしたり、プロキシを設置してアクセスを制限したりする際、クライアント側からするとサーバもプロキシも同じインターフェースで接続できるので、接続先がサーバからプロキシに変わったことを意識する必要はない。
  • コードオンデマンド
    • プログラムコードをサーバからダウンロードし、クラインアント側でそれを実行するアーキテクチャスタイル(例:JavaScript, Flash, Javaアプレット)
    • 利点:クライアントを後から拡張できること
    • 欠点:ネットワーク通信におけるプロトコルの可視性が低下すること。
  • ULCODC$SS = REST
    • クライアント/サーバ、ステートレスサーバ、キャッシュ、統一インタフェース、階層化システム、コードオンデマンドまでを追加した複合アーキテクチャスタイル=ULCODC$SS = REST

URI

  • リソースを統一的に識別するID
  • URIとHTTPメソッドの関係は、名詞と動詞の関係にある。

HTTP

  • TCP/IP
    • HTTPはTCP/IPをベースにしている。
    • 下からネットワークインタフェース層、インターネット層、トランスポート層、アプリケーション層の4層で構成されている。
    • ネットワークインタフェース層

      物理的なケーブルやネットワークアダプタに相当する部分
    • インターネット層

      ネットワークでデータを実際にやりとりする部分。TCP/IPではIPが相当する。データの基本的な通信単位をパケットと呼び、氏江地したIPアドレスを送り先として、パケット単位でデータをやりとりして通信する。自分のネットワークインタフェースでデータを送り出すことだけを保証している。(送り先まで届くかどうか保証しない)
    • トランスポート層

      IPが保証しなかったデータの転送を保証する役割を持つ。TCP/IPではTCPが相当する。
      TCPでは接続先の相手に対してコネクションを張る。このコネクションを使ってデータの抜け漏れをチェックし、データの到達を保証する。TCPで接続したコネクションで転送するデータが、どのアプリケーションにわたるかを決定するのがポート番号
    • アプリケーション層

      具体的なインターネットアプリケーションを実現する層。TCPでプログラムを作るときは、ソケットと呼ばれるライブラリを使うのが一般的。ソケットはネットワークでのデータのやりとりを抽象化したAPIで、接続、送信、受信、切断などの基本的な機能を備えている。

- **HTTPメソッド**
GET:リソースの取得(URIにキーワード含める)
POST:リソースの作成、追加(リクエストボディにキーワード含める)
PUT:リソースの更新、作成
**POSTとPUTの使い分け**
POSTでリソースを作成する場合、URIの決定権はサーバ側にあり(例:Twitter)、PUTの場合URIはクライアント側が決定する。(例:Wikipedia)
  • ステータスコード
    戦闘の数字で分類することで、クライアントはどのようなレスポンスを返したのかを理解でき、クライアント側でどのような処理をするべきかの大枠を知ることができる。

ハイパーメディアフォーマット

  • HTML
    • text/html(SGMLベース)とapplication/xhtml+xml(XMLベース)の2種類がある。
    • microformats

      リソースのリンク関係を表現するには、HTMLが定義しているリンク関係だけでは足りないため、HTMLのリンク関係の拡張がmicroformatsで行われている。
  • XML
    • 要素を入れ子にして表現する木構造
  • microformats

    HTML文書そのものにメタデータを埋め込む技術。リンクの細かい意味やイベント情報などを表現できる。(例:hCalendar)
  • ATOM
    • ブログなどの更新情報を配信するためのフィードで、幅広い分野での応用が可能な汎用XMLフォーマット。
  • JSON
    • XTML, microformats, AtomというXML系のリソース表現よりもより軽量なデータ表現形式。Ajax通信におけるデータフォーマットとして活用。
    • 文書をマークアップすることには向いていないが、ハッシュや配列といったプログラミング言語から扱いやすいデータ構造を記述できることが特徴。

感想

  • Webアーキテクチャの基本についての良い教科書だった。一方で、説明が足りないところや説明が分かりにくいところもそこそこあった。(例:TCP/IPの4層モデルについては軽く触れる程度で終わってしまった)大枠が概ね頭に入ったので、あとは他の本で補強していきたい。

5冊目: 「エンジニアの知的生産術」

エンジニアの知的生産術

エンジニアの知的生産術

新しく学んだこと

  • 写経はあくまでも補助輪。必ずしも必要ではない。
  • 記憶の定着のためには、何かを覚えた後にテストを受け、6か月後にまた復習すると最も記憶に定着する。
  • 参考書は大学のシラバスに載っている参考図書や、正誤表が充実しているもの(著者が改善していく気がある)、改定されている・ロングセラーであるもの選ぶ。
  • やる気を出すためにはゴールは明確にし、近くし、達成可能であることが大事である。
  • Matzのコードの読み方:全体を読もうとしない。面白そうなところをつまみ食いして、先人の知恵を学べば十分。また、目的を持って読めば、効果的に読解して知識を得ることができる。
  • ソースコードは段階的に読む:

    ①内部構造を開設したドキュメントがあればそれを読む

    ②ディレクトリ構造を読む

    ③ファイル構成を読む

    ④略語を調査する

    ⑤データ構造を知る

    ⑥関数どうしの呼び出し関係を把握する

    ⑦関数を読む
  • 全体像を掴むために、目次や章タイトルを先に読む。

感想

1章はエンジニア初心者が初期段階に読むのに非常に適していると思う。2章以降はやる気を出す、記憶を鍛える、効率的に読む、考えをまとめる、アイデアを思いつく、何を学ぶかを決める、など、エンジニア向けというよりは、一般的に何かを学ぶ人向けの情報と言ってよい。「エンジニアとしての学び方」と「他の学問の学び方」に特に違いは無い、ということだと理解した。

6冊目: プロを目指す人のためのRuby入門

Ruby入門

プロを目指す人のためのRuby入門

感想

文法書なので、内容面は割愛するが、これほどに論理的飛躍が無く、分かりやすい説明で、網羅的で、読み物として十分に面白い文法書に初めて出会った。

以上、長くなりましたが11月中に読んだ本6冊の紹介でした。

12月中に確定で読む技術書(また書評書きます)

マスタリングTCPIP

RealWorldHTTP

Docker実践入門

17
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?