Michi's Tech Blog

一人前のWebエンジニアを目指して

【書評】『プログラマー脳』を読んでみた

こんにちは!スマレジ テックファームのMichiです!

今回の記事は『プログラマー脳』という本を読んでみた書評になります。

プログラマー脳』とは?

脳の働きをより深く理解することで、プログラマーとしてのスキルや習慣を向上させることを目的とした本です。

長年プログラミング教育の研究に取り組んでいる著者が、最新の「認知科学」に基づいて、プログラミングの際のさまざまな作業や技術の取得を効率的に行うための方法を解説しています。

楽天ブックス: プログラマー脳 〜優れたプログラマーになるための認知科学に基づくアプローチ - Felienne Hermans - 9784798068534 : 本

最近、「どうすればもっと効率的にコードを読んだり書いたりすることができるだろうか」という悩みがあり、本書を手に取ってみました。

要約

私たちが何かを認知するとき、脳は異なる3つの認知プロセスを利用しています。それはプログラムを読み書きするときも例外ではありません。

その3つのプロセスとは、長期記憶、短期記憶、ワーキングメモリのことです。

次に、この3つのプロセスについて順に解説します。

長期記憶

長期記憶とは、私たちの記憶がすべて永続的に保持されている場所です。何年も前の出来事を思い出せたり、靴ひもを結ぶといった日常的な動作を意識せずに行えるのも、すべて長期記憶にその出来事や動作が記憶されているからです。

コンピュータに例えるなら、人間の長期記憶はストレージ(HDDやSSD)に当たります。

プログラミングでいえば、変数やfor・if文などを理解し、特に意識せずに読んだり書いたりできるのは、これらの概念が長期記憶に保存されているからです。

短期記憶

短期記憶とは、私たちの脳に入ってきた情報を一時的に保存するための場所です。たとえば、誰かが電話番号を読み上げたとき、その番号はすぐに長期記憶に入るわけではなく、いったん短期記憶に保存されます。

コンピュータに例えるなら、人間の短期記憶はRAM(メモリ)に当たります。

プログラミングでいえば、変数の中身や関数の処理内容、利用されているデータ構造などが一時的に短期記憶に保存されることによって、プログラムを解読することができます。

短期記憶に保存できる情報の個数は非常に小さく、どんな人でも2~6個の情報しか同時に保持できないと、最新の研究からわかっています。

ワーキングメモリ

ワーキングメモリとは、長期記憶や短期記憶の情報を利用して計算を行ったり、新しい考えやアイデアを形成する場所です。

コンピュータに例えるなら、人間のワーキングメモリはプロセッサ(CPU)に当たります。

プログラミングでいえば、関数の計算結果を求めるときや、アルゴリズムを書いているときにワーキングメモリを使用しています。

短期記憶と同様に、ワーキングメモリも一度に処理できる情報の量は2~6個であるとされています。

熟練したプログラマーは長期記憶を活用する

この3つの認知プロセスのうち、短期記憶とワーキングメモリに関しては個人差がほとんどなく、また後天的に伸ばすことも難しいとされています。

一方で、長期記憶には無限の可能性があり、個人の努力や工夫で伸ばすことが可能です。

そして、熟練したプログラマーはこの長期記憶に過去の経験が大量に蓄積されているため、短期記憶やワーキングメモリの不足を補うことができます。その結果、人よりも素早く大量のコードを書いたり読んだりすることができるのです。

感想

この本を読んだ感想をひとことで表すなら、

『良いプログラマーになるには、たくさんコードを書いてたくさんコードを読むしかない』

です。(いや、元も子もないな。)

これは私も前々から思ったいたことですが、「プログラミングという行為は、基本的には過去の経験・記憶から似たパターンを引っ張り出してきて、現状のコードに当てはめているに過ぎない」ということです。
(言語やライブラリの開発者など、ゼロからの構築が必要な場合は別だとは思いますが。)

この本では、そのことを科学的データを用いて解説しています。

また、プログラムに関する長期記憶を鍛える方法として、フラッシュカードを利用したり、紙に印刷したコードに手書きでコメントを入れるといった方法が紹介されていました。

たしかに効果はあるんでしょうが、現役のプログラマーがそれをする暇があるならもっと実務でコードに触れる方がいいのでは、というのが私の感想です。実務経験がない、プログラミング初心者にはよい方法かもしれません。

私が前々から思っていた仮説を、科学的データに基づいて証明できたという意味ではよい読書でした。

Zodに入門してみる

こんにちは!
スマレジ テックファームのMichiです!

今回の記事では、ZodというTypeScriptのライブラリに入門してみます。

Zodとは?

公式ドキュメントの説明が一番わかりやすかったので、和訳したものをそのまま転載します。

ZodはTypeScriptでスキーマ定義・バリデーション設定を行うライブラリです。 ここでいう「スキーマ」という用語は、単純な文字列から複雑なネストされたオブジェクトまで、あらゆるデータ型を広く指します。

Zodは可能な限り開発者フレンドリーで設計されています。ゴールは型宣言の重複をなくすことです。Zodではバリデーターを一度宣言すれば、Zodが自動的に静的なTypeScriptの型を推論してくれます。単純な型を複雑なデータ構造にまとめるのも簡単です。

使ってみる

スキーマとバリデーションの設計

会員情報のスキーマを設計するとします。Zodで定義する場合は次のようになります。

import { z } from "zod";

const userSchema = z.object({
  name: z.string().nonempty("アカウント名は必須です。"),
  email: z.string().email("有効なメールアドレスを入力してください。"),
  age: z
    .number()
    .int("整数で入力してください。")
    .nonnegative("0以上の値で入力してください。"),
  password: z
    .string()
    .regex(
      /^(?=.*[A-Z])(?=.*[a-z])(?=.*d)[A-Za-z0-9]{8,}$/,
      "パスワードは8文字以上で、英大文字・英小文字・数字を最低1回は使用してください。"
    )
});

各プロパティのデータ構造は以下の通りです。

  • name: 文字列。空入力を許可しない。
  • email: 文字列。メールアドレスの形式に沿ったデータのみ許容。
  • age: 数字。0以上の整数のみ許容。
  • passwod: パスワード。8文字以上で、英大文字・英子文字・数字の使用が最低1回は必要。

Zodの便利なところは、nonemptyemailのようにあらかじめ必要なバリデーション関数が用意されている点です。regex関数を使えば正規表現を使ってオリジナルのバリデーションを構築することもできます。

また、これらの引数にはエラーメッセージを登録することができます。これについては、また後ほど解説します。

TypeScriptの型生成

Zodで設計したスキーマからTyepScriptの型を逆生成できます。

type User = z.infer<typeof userSchema>;

User型の中身

これで、バリデーションと型を別々に定義する手間が省けます。

React Hook Formとの連携

Reactと連携して、画面側のバリデーションを作成します。

今回はZodとの相性が良いReact Hook Formを利用します。React Hook Formについての詳しい説明は本稿では省略するので、気になる方は公式ドキュメントやほかの解説記事をお読みください。

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import "./App.css";

// スキーマの作成
const userSchema = z.object({
  name: z.string().nonempty("アカウント名は必須です。"),
  email: z.string().email("有効なメールアドレスを入力してください。"),
  age: z
    .number()
    .int("整数で入力してください。")
    .nonnegative("0以上の値で入力してください。"),
  password: z
    .string()
    .regex(
      /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)[A-Za-z0-9]{8,}$/,
      "パスワードは8文字以上で、英大文字・英小文字・数字を最低1回は使用してください。"
    ),
});

// スキーマからTypeScriptの型を逆生成
type User = z.infer<typeof userSchema>;

// コンポーネント
const SingUpForm = () => {
  // React Hook Formの利用
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<User>({
    resolver: zodResolver(userSchema),
  });

  const onSubmit = (data: User) => {
    // フォームデータを送信する処理(省略)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>アカウント名</label>
        <input {...register("name")} />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email</label>
        <input {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>年齢</label>
        {/* フォームにバインドされる値は常に String型として扱われるため、
            React Hook Form側でNumber型に変換する */}
        <input {...register("age", { valueAsNumber: true })} />
        {errors.age && <p>{errors.age.message}</p>}
      </div>
      <div>
        <label>パスワード</label>
        <input type="password" {...register("password")} />
        {errors.password && <p>{errors.password.message}</p>}
      </div>
      <button type="submit">登録</button>
    </form>
  );
};

export default SingUpForm;

useFormの引数のresolverプロパティには、React Hook Formと連携しているバリデーションライブラリを設定できます。

今回はzodResolver設定し、引数には最初に定義したuserSchemaを渡します。

不正な値を入力しようとすると、React Hook Formが自動でZodと連携し、エラーメッセージを返してくれます。

まとめ

Zodについて紹介しました。

今回はフロントエンドだけでしたが、バックエンドもTypeScriptを採用している場合は、Zodで作成したスキーマと型をフロント/バック両方で使いまわせるので、とても便利ですよ。

【書評】『ノンデザイナーズ・デザインブック』を読んでみた

こんにちは!
スマレジ テックファームのMichiです!

今回の記事は『ノンデザイナーズ・デザインブック』という本を読んでみた書評になります。

『ノンデザイナーズ・デザインブック』とは?

『ノンデザイナーズ・デザインブック』とは、デザイナーではない人向けに書かれたデザインの入門本です。発売から25年以上経っていますが、いまでもデザインを学ぶ世界中の人々に読まれています。

「初心者でデザインのことを勉強したいんですが、何から始めればいいですか?」という人がいれば、ほぼ間違いなく「まずこれを読め」とおすすめされるデザインの鉄板本です。

私も最近、フロントエンドを触ることが多いのでデザインの知識が欲しいなと思い、読んでみました。

要約

  • 良いデザインには必ず存在する4つの基本原則がある

デザインの基本4原則

近接

  • 互いに関連する項目は近づけてグループ化する
  • グループ化することで、一つ一つのブロックに意味を持たせる
  • グループ化するには空白のコントロールが重要。均等な空白は情報の組織化を阻害する。

ファーストフード店のメニューの例。左側はすべての空白が均等なので、情報のグループがわかりにくい。一方右側はグループごとに近づけているので、メニューがどのカテゴリーに属しているのか一目でわかる。

整列

  • ページ上の要素には明確な意図をもって、他の要素と視覚的なつながりを持たせる配置をする
  • 一つのページの中で、複数の配列方法(たとえば中央揃えと右揃え)を使わない
  • なんでもかんでも中央揃えにしない。中央揃えはフォーマルであるが、少し弱い印象を与えることがある。

左揃えにすることで、読みやすくスッキリとした見た目になる

反復

  • デザイン上の何かの特徴を作品全体を通して繰り返す
  • 一貫した反復は作品全体に一体感と視覚的なおもしろさをもたらす
  • Webページのヘッダーやフッターはこのテクニックを使っている

ビュレットを入れるだけでも反復のテクニックを使える。また、各カテゴリーが太字で表記されているのも反復であるといえる。

コントラスト

  • 2つの異なる色、大きさ、線の太さ、書体、形などを使い、視覚的なおもしろさを作り出す
  • コントラストには私たちの目を引き付ける効果がある
  • コントラストを使うときは、はっきりと異ならせること。中途半端な違いはデザイン上のミスだと認識されることがある。

カテゴリーと個々のメニューで、文字の大きさ・書体・言語にコントラストが付いている

感想

世界中で25年も読まれているだけあって、デザインの基本原則が非常にシンプルにまとめられています。

この本だけでデザインのすべてを理解することは難しいかもしれませんが、右も左もわからないデザイン初心者には、「とりあえずここさえ気を付けておけばそれなりのものが作れる」というラインを学ぶ意味では良書だと思いました。

良くないところを挙げるとすれば、古い本だけあってどうしても紙媒体のデザインの話が中心でした。Webデザインを学びたい人は、この本の他にWebデザイン専門の書籍で学習する必要はあるかなと感じます。

あとこれは洋書あるあるなのですが、日本語の訳が微妙でした。

あまりにも翻訳がダイレクトすぎる文章がいくつかあり、2~3回読み直さないと意味が理解できなかったです。ここは翻訳家さんがもうちょっと意訳するなどして、日本人に読みやすい文章にしてほしかったなと思います。

【基本情報攻略日記】 - 第2回 受験の感想と効率的な勉強法 -

こんにちは!
スマレジ テックファームのMichiです!

前回の記事でご報告しました通り、このたび無事に基本情報技術者試験に合格しましたので、今回はその感想とこれから受験する人に向けての効率的な勉強法を書いていこうと思います。

受験の感想

受験前

試験勉強はちょうど合格の1か月前から始めました。

インプットには次の2つの教材を使用しました。

www.youtube.com

item.rakuten.co.jp

使い方は次のように実施しました。

  1. 最初にYotTubeで概要を理解する
  2. 本で細かいところまで網羅する
  3. 本の章末に付いている練習問題を解いてみる

8月は出社する機会が多かったので、YouTube動画を通勤中の電車内で視聴 → 休日など家にいる時間は本を読み進めるという流れでした。

過去問に真剣に取り組んだのは受験本番の3日前くらいからでした。(後でも言いますが、過去問はもっと早くからやっておいた方がいいです。)

あまり過去問を回す時間がなかったので、まともにやったものはほぼ下記の2つだけでした。

ただし、この2つの過去問は2週し、完璧に解ける状態にはしておきました。

ここまでの勉強時間は、平均1.5時間/日 × 1か月 = 45時間程度だったと思います。

試験前日の感触としては、「科目Aは何とかなるだろうけど、科目B(特にアルゴリズム)は自信がない」といった感じでした。

試験本番

※個別の設問の内容については規則で口外できないので、あしからずご了承ください。

科目A

科目Aは90分で60問を解く形式で、時間的には5分ほど余りました。
設問は勉強中に演習した問題(及びその類似問題)が多く、そこまで戸惑いはありませんでした。

科目B

科目Bは110分でアルゴリズム16問、情報セキュリティ4問の合計20問を解く形式です。

私は後半の情報セキュリティから先に解きました。理由は頭がまだ疲れていないうちに、確実に点数が取れる情報セキュリティの分野で得点しておきたかったからです。(結果的にこれは正解でした)

アルゴリズム問題は、見たことがあるような形式もあれば、初見のものもあり少し戸惑いました。特に、うち2問は完全にお手上げで、時間的な余裕がなかったこともあり適当に選択肢から回答しました。

試験時間は上記の2問をすっ飛ばしたうえでカツカツだったので、実質時間内に解ききれませんでした。

受験後

受験後すぐに結果が発表されました。

  • 科目A ... 720/1000 点
  • 科目B ... 715/1000 点

率直な感想としては、「科目Aはもっと取れたと思っていたし、科目Bは思ったより結果が良かった」です。結果的にA/Bどちらもほぼ同じ点数というのは意外でした。

これはIRT方式で、難しい問題の配点は高く、逆に簡単な問題の配点は低く算出されることが影響しているのかもしれません。

何はともあれ、無事1回目の受験で基本情報技術者試験に合格することができました。

これから勉強をはじめるなら

これから基本情報技術者試験を受ける方に向けて、効率の良い勉強方法を考えてみたいと思います。

なお、ここではなるべく最速で試験に合格することだけを考えるので、IT職として必要な知識を身につけられるかということについては度外視します。

勉強方法

まず、教材としては次の3つを使用します。

www.youtube.com

www.fe-siken.com

books.rakuten.co.jp

ここで先ほど挙げた、「『栢木先生の基本情報技術者教室』は?」と思われた方もいるかもしれません。

実際に一通り読破した身としては、この類の参考書は必要ないかなと思いました。

理由は次の2つです。

  • YouTubeの動画が網羅的かつわかりやすく説明してくれているので、わざわざ本で学びなおす必要がない
  • インプットよりアウトプット(過去問演習)に時間を割きたい

どうしてもという方は本を買ってもよいですが、全部読まずに辞書的な使い方に留めるのがよいと思います。

勉強のやり方としては次の順番で行います。

  1. YouTubeでインプット
  2. 過去問道場でアウトプット
  3. パーフェクトラーニングで本番形式に慣れる

ここで気をつけるべきなのは、1と2は同時並行で進めることです。

なぜなら、1を全部終わらせてから2をやろうと思っても、その頃にはせっかくインプットした内容を忘れているからです。

なので、『YouTubeでひとつの単元を視聴する → その範囲の過去問を解く』のサイクルを遅くても3日以内でやりたいところです。

3については試験本番の1週間前くらいから始めましょう。 やるときは時間を測って、試験本番の時間配分に慣れておきましょう。

勉強時間

巷では、基本情報合格までに必要な勉強時間は以下のとおりだと言われています。

  • IT業界で働いているなど、すでに基本的な知識を有している人 ... 50時間
  • ITにあまり馴染みがなく、基本的な知識がない人 ... 200時間

自分の場合はWebエンジニア歴1年で一つ目の条件にあてはまりましたが、だいたい45時間程度で合格できたので、この情報は体感あっていると思います。

ただし、自分は大学は文系学部(法学部)を卒業しており、12年間まともに数学に触れていないというハンデはありました。実際に勉強時間のうちの少くなくない部分を、高校数学の思い出しに費やしたという面があります。

理系学部、または工業系の高専を卒業されている方などは、数学の部分でアドバンテージが取れるので、実際はもっと少ない時間(30時間程度)で合格できるのではないかと思います。

試験前日

試験前日はちゃんと寝ましょう。(大真面目に言っています)

計算問題やアルゴリズムで頭を使うので、睡眠不足は思考力の低下を引き起こし、ケアレスミスが発生します。

前日に詰め込んだところでたかが知れているので、潔く諦めて次の日にすっきりとした頭で受験できるほうが得策です。

試験本番

科目A

特にいうことはないです。出題された問題から順に解いていきましょう。

十分に過去問演習を行なっていれば、科目Aで時間が足りなくなるということはないと思います。

科目B

こちらは少し解き方に工夫が必要です。

まず、後半の情報セキュリティ分野4問から先に解きます。理由は、

  • アルゴリズム問題で頭がまだ疲れていないうちに、確実に情報セキュリティ分野で点を取りたい
  • 科目Bは時間が足りなくなりがちなので、後半の焦った状態で情報セキュリティ問題を解くとミスする可能性がある

からです。

情報セキュリティ分野は基本的に、落ち着いて問題を読むことさえできれば全問正解できます。ここで4問すべてに正解しておけば、残りのアルゴリズムでは8/16問正解すれば科目Bに合格でき*1、この後がだいぶ楽になります。

アルゴリズム問題では過去問やサンプル問題の傾向からして、基本情報受験者レベルの知識では通常解けないような設問が1〜2問出題されます。(数学的知識が必要な問題やクイックソートの実装など)

そのような問題を長時間考えても仕方ないので、この場合は適当に選択肢から回答して次の問題へ行きましょう。どうせほとんどの人は解けない問題なので、落としても問題ありません。

それよりも大事なのは、確実に解けるような問題をミスなく、なるべく早く解くことです。

そのためにも、過去問演習で多くの問題にあたり、解答のパターンを知っておくことが大事です。

まとめ

基本情報技術者試験を受験した体験談と、そこから得た効率的な勉強法について書きました。

余談ですが、「応用情報は基本情報を取った後にすぐ受験した方がよい」と聞いたので調べてみたら、10月試験の申し込みはとっくに終わっていました...笑

次は来年の4月になるらしいですが、どうしましょうか。

*1:IRT方式なので実際には100%合格できるとは限らないが、目安としてこう書いています

WindowsでNext.jsの環境構築をするときのTips

こんにちは!
スマレジ テックファームのMichiです!

先日、WindowsでNext.jsの環境構築をしているときに、いくつかOS起因で詰まったところがあったので、メモとして残しておきます。

環境

opensslコマンドがない

NextAuth.jsを使用する際、シークレットキーとしてNEXTAUTH_SECRET環境変数を設定します。

その際、通常はopensslコマンドを使用しますが、Windows PowerShell、およびコマンドプロンプトにはopensslコマンドが存在しません。

正攻法はWindowsにopensslをインストールすることです。

oji-cloud.net

ですが、「シークレットキーを設定するためだけにわざわざopensslをインストールするのがめんどくさい!」という方のために、今回は別の解決法を用意します。

解決法

CrypTool-Onlineというサービスがあります。

www.cryptool.org

リンク先から真ん中の「OpenSSL」をクリックします。

CLIが現れるので、openssl rand -base64 32のようにコマンドを入力すると、ランダムな値を生成してくれます。

このサービスを使えば、OSにopensslをインストールしなくても簡単にシークレットキーを作成することができます。

「'NODE_OPTIONS' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチファイルとして認識されていません。」

package.jsonscripts.devNODE_OPTIONSを指定するとき、Windowsだとエラーが出てうまくいきません。

package.json

"scripts": {
    "dev": "NODE_OPTIONS='--inspect' next dev",
  },

"NODE_OPTIONS='--inspect'" は、Node.jsプロセスをデバッグモードで起動するためのものです。

ちなみに、"NODE_ENV=productionのように、NODE_ENVを指定した場合も同様のエラーが発生します。

PowerShell

PS C:\Users\michi\Develop\sample> yarn dev
yarn run v1.22.19
$ NODE_OPTIONS='--inspect' next dev -p 4000
'NODE_OPTIONS' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

解決法

win-node-envというパッケージを入れることで解決します。

以下のコマンドでwin-node-envをグローバルにインストールします。

npm install -g win-node-env

これでyarn dev(npm run dev)が通るようになります。

このようなエラーが起こる原因としては、「bableコマンドを実行する前にNODE_ENVを設定しても、Windowsでは動作しないから」ということらしいです。

環境変数からポート番号を認識してくれない

次のように、package.json内でNext.jsが使用するポート番号を指定します。

なお、Next.jsは.envファイルから環境変数を読み込めないので、事前にシェル上で環境変数PORTをエクスポートしておきます。

PowerShell

$env:PORT = 4000

package.json

"scripts": {
    "dev": "NODE_OPTIONS='--inspect' next dev -p $PORT",
  }

ですがこれはうまくいきません。
エラー文を読んでみると、環境変数が読み込めていないようです。

PowerShell

PS C:\Users\michi\Develop\sample> yarn dev
yarn run v1.22.19
$ NODE_OPTIONS='--inspect' next dev -p $PORT
Debugger listening on ws://127.0.0.1:9229/1d961b41-8158-48f8-a63b-18435e632ad2
For help, see: https://nodejs.org/en/docs/inspector
RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received type number (NaN).

-- (省略)--

at async nextDev (C:\Users\michi\Develop\sample\node_modules\next\dist\cli\next-dev.js:498:9) {
code: 'ERR_SOCKET_BAD_PORT'
}
Note: This command was run via npm module 'win-node-env'
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

解決法

結論として、現時点ではWindowsから環境変数を使ってNext.jsのポート番号を指定する方法あありません。(cross-envを使えばできるという説もありますが、自分の環境ではうまくいきませんでした。)

ここはおとなしく、package.json内でポート番号を直接指定するしかありません。

"scripts": {
    "dev": "NODE_OPTIONS='--inspect' next dev -p 4000",
  }

参考

github.com

まとめ

Unix系とWindowsで結構な環境差分があることがわかったので、特に理由がない限りはMacで環境構築したほうがよさそうです。もしくは、WindowsでWSLを使うとか。

【基本情報攻略日記】 - 第1回 試験内容の理解と対策 -

こんにちは!
スマレジ テックファームのMichiです!

前回の記事でも書いた通り、今月から基本情報技術者試験の合格を目指して勉強を開始します。

今回は第1回目ということで、試験内容の理解と対策を進めます。

基本情報技術者試験とは?

試験概要

知らない人はいないと思いますが、念のため。

基本情報技術者試験は、ICTや情報処理の分野の国家試験として数十年の歴史をもつ「情報処理技術者試験」の区分の一つです。情報処理技術者試験は、「情報処理の促進に関する法律」に基づき、情報処理に関する業務を行う者の技術の向上に資するために経済産業大臣が行っている国家試験なので、基本情報技術者試験は、その名の通り、情報処理技術に関する基本的な知識・技能を有していることを国が認定するものと言えます。

引用 - 基本情報技術者ドットコム

要するに、『IT業界への登竜門的な国家試験』ということですね。

たしかに、新卒からSIerなどに就職した大学時代の友人などは、1年目で会社から取らされていた記憶があります。

逆に、Web業界ではそこそこの歴の人でも持っていなかったりすることもあるので、このへんは文化の違いでしょうか。

試験形式の変更

これも有名な話ですが、2023年4月から試験の形式が大幅に変更になりました。

主な変更点は次の通りです。

  1. 年間通して試験が実施されるようになった。以前は年2回だけの試験だったものが、CBT方式で受験者が自分の希望する日時に試験を受けられるようになった。

  2. 試験時間が300分から190分へ短縮された。問題数も午前試験(科目A)が80問から60問へ減少、午後試験(科目B)は大問5つを回答する方式から小問20個を回答する方式へ変更された。

  3. 午後試験の選択問題が廃止され、情報セキュリティとアルゴリズムに焦点を当てた形式に変更された。また、それまで難易度が高いとされていた大問(長文問題)が廃止され、小問形式へ変更された。

  4. プログラミング言語の選択が廃止された。午後試験のアルゴリズム問題で、これまでは受験者が複数のプログラミング言語Java, Pythonなど)の中から自由に選択できたが、それが廃止され疑似的なプログラミング言語に統一された。

これだけ書くとかなり大きな変更に見えますが、旧午前試験(科目A)については、これまで通り過去問から出題されることが予想されます。

旧午後試験(科目B)は形式がガラッと変わりましたが、実質的な試験範囲の減少と長文読解問題が消滅したため、以前より易化したのではないかという見方が多いようです。

分析

個人的にポイントだと思う点をまとめます。

  • 設問はすべて選択式の4択である
  • 科目A/B それぞれで600/1000点以上取得で合格
  • 30日以上のインターバルを開ければ何回でも受験することが可能
  • 出題は基本的に過去問から出ることが多い

個人的にここで気になったのが、4択式で600点以上取れば合格というところです。

4択式なので、当てずっぽうで適当に答えても、理論上は25%の確率で正解することになります。となると、実際に実力で正解しなければならない問題の割合はもっと低いはずです。

ちょっと真面目に計算してみましょう。

問題を解くとき、答えを知らない場合だけ適当に解答すると仮定します。答えを知っている問題の割合をxとすると、合格するために必要な全体の正解率は次のように表されます。

x (知っている問題で正解する場合) + (1-x)*0.25 (知らない問題で適当に解答して正解する場合) = 0.60(合格に必要な正答率)

これを解くと、x ≒ 0.467です。

つまり、全体のうち約46.7%の問題を完璧に答えることができれば、試験には合格できることになります。

実際は運が悪いと、当てずっぽうで回答したときの確率が25%を下回ったり、自分の知らない範囲の問題ばかり出題される可能性もあるので、保険はかけておきたいところですが。

それでも、全体の6割くらいをマスターできていれば、安全に試験に合格することができそうです。

対策

中期計画(8~10月)

前回も書きましたが、以下のような計画で進めます。

  • 8月:試験問題の基礎的な内容の理解
  • 9月:過去問演習
  • 10月:試験合格

あと、試験は毎月受けます。理由は、

  • このような試験は受験回数が多くなるほど点数が上がる
  • 合格点(600/1000点)以上の勉強をしたくないから

です。

ひとつめの理由は、受験回数が多くなるほどテスト形式に慣れて、点数アップが見込めるということです。過去問でも問題の演習はできますが、やはり実際に試験会場に行って、当日の流れや雰囲気を早いうちに掴んでおく方がいいと思っています。

せっかくいつでも受験できるようになったので、そのメリットを生かします。

ふたつめの理由は少し不純ですが、「3ヶ月フルに勉強した結果、9割正解して合格」みたいなことになるのはもったいないと感じています。それならば、「2カ月しか勉強していないけど、6割正解で合格」のほうがコスパがいいです。

賛否両論ありますが、僕は「資格は資格、実務とは別」と考えています。なので、こういった資格系の勉強はなるべく早く終わらせて、その分実務で使う技術などの勉強に時間を当てほうが有意義だという考えです。

「8月は偵察、9月で受かればラッキー、10月は絶対に合格する」といった意気込みでやっていきたいと思います。 (受験料7,500円がもったいないですけどね。)

短期計画(8月)

というわけで、今月は試験問題の基礎的な内容の理解を進めていきます。

教材は定番のこちらの書籍を購入しました。

item.rakuten.co.jp

で、少しやってみたのですが、これが全然わからん(笑)

浮動小数点の計算とかなんのこっちゃって感じです。この教材で一から勉強するのはちょっと効率が悪いなと思っていたところに、いい素材を見つけました。

www.youtube.com

「あなたが神か...!」

この動画、試験範囲を網羅的にまとめてくれていて、それでかつ説明がすごくわかりやすいです。こんなに良い教材が無料で観れてしまうとは、まさしく神です。

というわけで、今は次のような流れで勉強しています。

  1. YouTube動画で概要をサラッと把握する
  2. 参考書で細かい概念を補足する
  3. 参考書の各章末にある練習問題を解いてみる

とりあえず、8月中はこの方法で学習を進めていきます。

まとめ

さっそく、8月末の試験に申し込みました。

無事にマイナス1次試験突破です🎊

【React】モバイル版のタッチイベントが発火しない問題に対処する

こんにちは!
スマレジ テックファームのMichiです!

先日、Reactを使ったアプリ開発において、モバイル版のタッチイベントが発生しないというバグに遭遇しました。そして、このバグがReactのライブラリやブラウザの仕組みにも深く関わっているということがわかったので、忘れないように忘備録としてまとめておきます。

※この問題については自分も完全に理解しているとはいえず、まだまだわかっていないことも多いです。 そのため、間違った情報も含んでいるかもしれませんが、その際はブログのコメントやTwitterから教えていただけると助かります。

対象バージョン

ライブラリ

  • React: 17.0.2
  • react-calendar-timeline: 0.28.0

ブラウザ

遭遇した現象

次のようにReactとReact-time-calendarというライブラリを使用したコンポーネントがあります。React-time-calendarはReactのコンポーネントを使ってカレンダーのタイムライン表示を描画できるライブラリです。

import React from "react";
import Timeline from "react-calendar-timeline";

// タイムライン上のカスタムアイテム
const CustomItem = ({ item, getItemProps }) => {

  // touchstartイベントが発火した時の処理
  const handleTouchStart = () => {
    console.log("Item was touched");
  };

  return (
    <div {...getItemProps({ onTouchStart: handleTouchStart })}>
      {item.content}
    </div>
  );
};

// タイムラインコンポーネント
const MyTimeline = ({ groups, items }) => {
  return (
    <Timeline
      groups={groups}
      items={items}
      itemRenderer={({ item, getItemProps }) => (
        <CustomItem item={item} getItemProps={getItemProps} />
      )}
    />
  );
};

export default MyTimeline;

このコードはデスクトップでは問題なく動作しますが、一部のモバイルブラウザでは期待通りに動作しない可能性があります。

自分が遭遇した現象としては、以下の通りです。

  • カレンダーのアイテムをタッチしても、イベントが発生しない
  • モバイル版Chromeでは画面をタッチしたりスクロールすると、Unable to preventDefault inside passive event listener invocationというエラーメッセージがコンソールに表示される
  • Safariではコンソールエラーは発生しないが、画面をタッチしてもイベントが発火しない現象は存在する

※実際に自分が遭遇したコードはもっと複雑ですが、抽象化するためにあえて簡略にしています。そのため、このコードでは上記のような現象が起こるかはわかりません。

次項では、

  • タッチイベントが発生しない
  • コンソールにエラーが出る

という2つの問題を切り離して、別々に考えていきます。

解決策

タッチイベントが発生しない現象

まず、この現象を理解するために、ReactのDOMイベントの処理方法について知る必要があります。

Reactは実際のDOMイベントを直接処理するのではなく、合成イベント(SyntheticEvent)と呼ばれるReactのイベントシステムを使用します。これにより、ブラウザや環境に関係なく一貫したイベント処理を行うことができます。

しかし、Reactの合成イベントはモバイルブラウザ上の一部のイベント(特にTouchEvent)に対していくつかの問題を引き起こすことが知られています。具体的には、モバイルブラウザではtouchstartイベントが一部の要素でキャンセルされることがあります。

これを解決するためには、React由来ではないネイティブのDOMイベントリスナーを手動で登録することが必要です。

import React, { useEffect, useRef } from "react";
import Timeline from "react-calendar-timeline";

// タイムライン上のカスタムアイテム
const CustomItem = ({ item, getItemProps }) => {
  const itemRef = useRef(null);

    // touchstartイベントが発火した時の処理
    const handleTouchStart = (event) => {
       // デフォルトのtouchstartイベントに登録されている処理をキャンセル      
       event.preventDefault();
       console.log("Item was touched");
    };

  useEffect(() => {
    // 手動でtouchstartのイベントリスナーを登録する(パッシブモードはOFFにする ※後述)
    const itemElement = itemRef.current;
    itemElement.addEventListener("touchstart", handleTouchStart, { passive: false });

    // クリーンアップ関数(イベントリスナーを削除)
    return () => {
      itemElement.removeEventListener("touchstart", handleTouchStart);
    };
  }, [handleTouchStart]);

  return (
     // 登録したイベントリスナーをrefを使って参照する
    <div {...getItemProps({ ref: itemRef })}>
      {item.content}
    </div>
  );
};

// タイムラインコンポーネント
const MyTimeline = ({ groups, items }) => {
  return (
    <Timeline
      groups={groups}
      items={items}
      itemRenderer={({ item, getItemProps }) => (
        <CustomItem item={item} getItemProps={getItemProps} />
      )}
    />
  );
};

export default MyTimeline;

ここでのポイントは、useEffect内でクリーアップ関数を使用し、コンポーネントのアンマウント時に登録したイベントリスナーを削除してあげることです。

これには、CustomItemコンポーネントが再レンダリングされるたびに新しいイベントが登録され、溜まっていくことを防ぐ意味があります。

これで、モバイルブラウザから対象をタッチしても、イベントを発生させることができます。

コンソールに警告が出る現象

ただし、上記の修正だけでは、Unable to preventDefault inside passive event listener invocationの警告を消すことができません。

このエラーは、パッシブイベントリスナーがevent.preventDefault()を呼び出すことはできないというブラウザの制約に関連しています。

これだけ説明しても「は?」という感じなので、順を追って説明します。

JavaScriptのpreventDefault()

event.preventDefault()JavaScriptでイベント処理を行う際に、ライブラリやフレームワークの中でよく使われるメソッドです。このメソッドは、特定のイベントがブラウザによって自動的に実行されることを防ぎます。言い換えれば、ブラウザのデフォルトの挙動をキャンセルするということです。

記事の最初の方に、Reactは合成イベント(SyntheticEvent)と呼ばれる独自のイベントシステムを使用すると書きました。このイベントシステムを利用するためには、ブラウザのデフォルトのイベントシステムをOFFにする必要があります。その際に、このpreventDefault()が使用されています。

ブラウザのパッシブモード

ブラウザのイベントリスナーにはアクティブ(能動)とパッシブ(受動)の2つのモードが存在します。

パッシブモードでは、ユーザーの操作そのものを阻止(例えば、スクロールを止めるなど)することなく、その操作に対する反応だけを受動的に実行します。モバイルデバイスではパフォーマンスを向上させるために、多くのタッチやスクロールのイベントリスナーはデフォルトでパッシブになっています。これにより、イベントリスナーが動作を遅延させることなく、スムーズなユーザー体験を提供することができるためです。

パッシブモードでpreventDefault()を実行できない

さて、予備知識の解説が終わったので本題に戻ります。

デフォルトでパッシブになっているtouchstartイベントに対して、ReactのSyntheticEventがpreventDefault()を呼び出します。が、ここで問題が発生します。

「パッシブなイベントリスナーは、preventDefault()を呼び出せない」というルールが存在するからです。(そもそも、パッシブモードはユーザーの操作を阻止しないという機能ですので、当然ですね。)

そのため、先述の解決法ではイベントリスナーの登録の際、{ passive: false }としていました。

// 手動でtouchstartのイベントリスナーを登録する(パッシブモードはOFFにする)
const itemElement = itemRef.current;
itemElement.addEventListener("touchstart", handleTouchStart, { passive: false });

しかしながら、ReactのSyntheticEventで登録されているtouchstartイベントリスナーは{ passive: true }のため、そちらも同時に呼び出されることによってpreventDefault()のエラーが発生してしまいます。

React(react-dom)の中身。preventDefault()を呼び出そうとして失敗している。

私がバグに遭遇した環境では、残念ながらこのコンソールエラーを消去する方法はみつかりませんでした。React内で{ passive: true }に制御している以上、それを直す方法はないという結論でチーム内でも検討を終えました。

Safariではなぜコンソールエラーが発生しないのか?

最後に、なぜSafariではUnable to preventDefault inside passive event listener invocationの警告が発生しないのかについて触れておきます。

こちらの表をご覧ください。

引用: https://github.com/facebook/react/pull/19654

赤線で囲まれた箇所を確認すると、Safariではタッチやスクロールのイベントリスナーはデフォルトでパッシブモードにならないことがわかります。

ゆえに、Safariではコンソールのエラーが発生しなかったわけです。ただし、ReactのSyntheticEventでpreventDefaultされていることには変わりないので、タッチイベントはChromeと同じで発生しなかったというわけです。

まとめ

  • Reactは独自の合成イベントを使用するため、モバイル版の一部のイベントはキャンセルされることがある
  • キャンセルされるイベントを起こしたい場合は、addEventListenerで手動で登録する
  • Reactではタッチやスクロールのような一部イベントリスナーはパッシブモードとなり、preventDefaultできない
  • ただし、Safariでは上記イベント時にデフォルトでパッシブモードにならないので、preventDefault可能

参考

developer.mozilla.org

qiita.com

www.pandanoir.info

github.com