LeetCodeでちょっと勉強する

LeetCodeというサイトを知った

競技プログラミングや企業採用時のコーディングテスト対策に使われる「LeetCode」というサイトがあるようで、 ちょっと試しに1問といてみた。

設問

「Shuffle the Array」という、難易度としては低いらしい問題で、配列の並べ替えのようだ。 [x1,x2,x3...xn,y1,y2,y3...yn]という配列を`[x1,y1,x2,y2,x3,y3,...xn,yn]という形に並び替えるらしい。

私の書いたコード

class Solution:
    def shuffle(self, nums: List[int], n: int) -> List[int]:
        numsx = nums[:n]
        numsy = nums[n:]
        i = 0
        returnnums = []
        
        while i < n:
            returnnums.append(numsx[i])
            returnnums.append(numsy[i])
            i += 1
            
        return returnnums

xの値とyの値を取り出して、新しい配列にx y x yと順に追加するコードを書いた。

正解はしたものの、以下のような感じのメッセージが記載された。

Runtime: 56 ms, faster than 95.07% of Python3 online submissions for Shuffle the Array.
Memory Usage: 14.1 MB, less than 14.06% of Python3 online submissions for Shuffle the Array.

どうやら、速度はまぁいいけどメモリ使用量が多いよ!みたいなことが書いてあるようだ。

メモリ使用量の少ないコードは以下のようだ

class Solution:
    def shuffle(self, nums: List[int], n: int) -> List[int]:
        res = []
        for i in range(n):
            res.append(nums[i])
            res.append(nums[n + i])
        return res

なるほど、range(n)を使えばfor文で回せるのか、とかなり初歩的であろう気づきを得る。 そしてどうやらforのほうが処理も早いらしい。じゃあ速度はいいけど、っていうのも信頼性がないな。論外っぽいぞ…

あと、上のコードはわざわざいちいちxとyを取り出すなんてこともしていない。無駄処理が多い…。

こういう常識みたいなのを知っていく必要がありそうだ…。

DjangoでStripe決済

こんなことがしたかった

  • Djangoで作っているアプリに、クレジットカード決済を導入したかった

決済系の機能をつけたかったので、外部サービスを利用しなんとか決済できないかと調査したところ「Stripe」というサービスがどうも使いやすそうだということで、とりあえず入れてみることにした。 ※まだ本番運用はしていない

情報があるようでないようで、結局ドキュメントを読みながら組み立てていったのでそれをもとに備忘録として残す。

こんな感じ

仕組みとしては、

  1. ブラウザ側で画面を読み込む際、Javascriptからサーバのエンドポイントに通信を行う
  2. 通信を受けたサーバ側で、Stripe側に準備されているPaymentIntent APIに通信を行い、で「PaymentIntent」なるものを生成する
  3. その「PaymentIntent」から「clientsecret」という値を取得しブラウザ側に返す
  4. うまくいったらブラウザ側にクレジットカード情報を入力するフォームが作成される
  5. そのフォームにクレジットカード情報を入力し、「支払う」ボタンを押すと、Stripe側が準備したJavascriptの関数で、入力したカード情報とclientsecretを利用し決済処理を行う
  6. 決済に成功したか失敗したかの情報がブラウザ側に返ってくるので、その情報をもとに成功メッセージやエラーメッセージを表示させるなどの処理を行う

という形のようだ。

サンプルコードは色々用意されているので、参考にしながら書けば機能する。

主に実装する必要があるのは決済用の画面以外では「2.のサーバのエンドポイント」「6.の成功/失敗後の処理」くらいではなかろうか。

実装の前に

Python用のStripeパッケージをインストールする必要がある。

pip3 install stripe でOK。

サーバのエンドポイント

サンプルコードのページによるとFlaskでは以下のように実装するようだ(私はFlaskはよくわからないが…)

  • server.py
import json
import os
import stripe
# This is your real test secret API key.
stripe.api_key = "シークレットキー"
from flask import Flask, render_template, jsonify, request

app = Flask(__name__, static_folder=".",
            static_url_path="", template_folder=".")

※中略

@app.route('/create-payment-intent', methods=['POST'])
def create_payment():
    try:
        #①ブラウザから送られたjsonを読み込む
        data = json.loads(request.data)
        #②stripe.PaymentIntent.createでPaymentIntent APIに通信する
        intent = stripe.PaymentIntent.create(
        #③APIに渡す値を記載。最低限「amount」と「currency」をjson形式で渡せば良い
        #ちなみに「calculate_order_amount」は商品名を渡して金額を計算するロジックのようなので、特に気にせずともよい。
            amount=calculate_order_amount(data['items']),
            currency='usd'
        )
        #④APIから返ってきた値のうち、clientsecretをブラウザに返す
        return jsonify({
          'clientSecret': intent['client_secret']
        })
    except Exception as e:
        return jsonify(error=str(e)), 403

if __name__ == '__main__':
    app.run()

こんなことが書いてあるみたいだ。これをDjangoで同じように書けばいい。

①の前段としてブラウザからjsonを送る際は、JavaScriptからPOSTで送るので、Djangoの場合csrf_tokenを一緒に送るかデコレータで回避するかしないと失敗するので注意。

ただ、送っているjsonデータは商品名だけなので、もう価格が決まっているのであればエンドポイントに通信させず、サーバ側で画面描画時にclient_secretを取得するところまでやってしまっても良いかもしれない。あとで試してみよう。

※追記:エンドポイントなしでできた。

また、③で渡せる値は結構多種多様なようで、渡す値によってはStripeのコンソールにも表示が可能だ。例えば「description」。

amount=1600,
currency='jpy'
description='説明'

という感じで投げると、Stripeのコンソール側には以下のように表示される。 f:id:ryori0925:20200614224848p:plain

コンソール側を見やすくするためにも何らかの値は渡してもいいかもしれない。

Stripe API Reference - Create a PaymentIntentに詳細は記載がある。

クライアント側のJS

サンプルコードのページによるとJS側は以下のように実装するようだ。

  • client.js
// パブリックキーを記載。
var stripe = Stripe("パブリックキー");
// PaymentIntentを作成するときにBodyに入れて送るjson。例では商品を送っている。
var purchase = {
  items: [{ id: "xl-tshirt" }]
};
// Stripeの準備ができるまではボタンを無効化
document.querySelector("button").disabled = true;

// ①サーバ側のエンドポイントにPOST通信し、問題なければフォームを作ったりしている
fetch("/create-payment-intent", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(purchase)
})
  .then(function(result) {
    return result.json();
  })
  .then(function(data) {
    var elements = stripe.elements();
    var style = {
      base: {
        color: "#32325d",
        fontFamily: 'Arial, sans-serif',
        fontSmoothing: "antialiased",
        fontSize: "16px",
        "::placeholder": {
          color: "#32325d"
        }
      },
      invalid: {
        fontFamily: 'Arial, sans-serif',
        color: "#fa755a",
        iconColor: "#fa755a"
      }
    };
    var card = elements.create("card", { style: style });
    card.mount("#card-element");
    card.on("change", function (event) {
      document.querySelector("button").disabled = event.empty;
      document.querySelector("#card-errors").textContent = event.error ? event.error.message : "";
    });

 // ボタンが押されたら決済処理を行う
    var form = document.getElementById("payment-form");
    form.addEventListener("submit", function(event) {
      event.preventDefault();
      payWithCard(stripe, card, data.clientSecret);
    });
  });

// 決済の実処理
var payWithCard = function(stripe, card, clientSecret) {
  loading(true);
  stripe
    .confirmCardPayment(clientSecret, {
      payment_method: {
        card: card
      }
    })
    .then(function(result) {
      if (result.error) {
        // Show error to your customer
        showError(result.error.message);
      } else {
        // The payment succeeded!
        orderComplete(result.paymentIntent.id);
      }
    });
};

// ②決済がうまく行ったときの後処理
var orderComplete = function(paymentIntentId) {
  loading(false);
  document
    .querySelector(".result-message a")
    .setAttribute(
      "href",
      "https://dashboard.stripe.com/test/payments/" + paymentIntentId
    );
  document.querySelector(".result-message").classList.remove("hidden");
  document.querySelector("button").disabled = true;
};

// ③決済が失敗したときの後処理
var showError = function(errorMsgText) {
  loading(false);
  var errorMsg = document.querySelector("#card-errors");
  errorMsg.textContent = errorMsgText;
  setTimeout(function() {
    errorMsg.textContent = "";
  }, 4000);
};

※後略

概ねコピペすればよいのだが、対応が必要な部分は3点。

①については、csrf_tokenをHeaderに入れた上で通信するなどの対応が必要。

②③については、入れるシステムによって好きに対応する形となる。私は完了ページにリダイレクトさせるような形をとった。

その他

決済用ページには少なくとも以下2つを読み込ませる必要がある。

<script src="https://js.stripe.com/v3/"></script>
<script src="/client.js" defer></script>

上記でカスタムするclient.jsはdeferで読み込むこと、別途stripeのjsを読み込むことが必要。

実装してみて

ちょっと厄介だったのが、エンドポイントを作らなければならない点。なるべくなら作りたくない。

決済金額は決済画面にアクセスした時点で決定している場合、contextか何かにclientsecretを入れてしまい、 それを取ってきてフォームを生成する…とかしてしまえばいいのかなどうかな、と考えている。 (clientsecret自体はどうせ通信で返ってきちゃうので、見えても問題ない…?)

参考文献

[Django]Stipeを使ってECサイトを作る │ 機械系エンジニア奮闘記

Accept a card payment | Stripe Payments

Stripe API Reference - Create a PaymentIntent

FullCalendarでビューポートのサイズに応じてデフォルトのカレンダー形式を切り替え

こんなことがしたかった

  • FullCalendarのデフォルト表示形式を、スマホとPCで切り替えたかった

Djangoで作っている予定管理アプリのようなものを、スマホで見た場合月間カレンダー形式だと分かりづらかったので、リスト表示にしたかった。 今回は完全にFullCalendarの話。

こんな感じ

FullCalendarのオプションである「defaultView」をビューポートの横幅に応じて変えましょうという試み。

PC表示(defaultView:dayGridMonth)

f:id:ryori0925:20200610003715p:plain

スマホ表示(defaultView:listYear)

f:id:ryori0925:20200610004030p:plain

プログラムは以下。動かなかったらごめんなさい。あとjs苦手なので汚いかもです。

※前提として、FullCalendarに必要なjsやcssは読み込まれていること

<script>
// コンテンツが読み込まれてから発火
    document.addEventListener('DOMContentLoaded', function() {
        var calendarEl = document.getElementById('calendar');

  // FullCalendarのオプション設定
        var options = {
            locale:'ja',
            plugins: [  'dayGrid', 'list'],
   〜オプション中略〜
            // PC用のdefaultViewを設定
            defaultView:'dayGridMonth',
   〜オプション中略〜
            events: [
     (表示イベントを書く)
            ]
        };

  // ウィンドウのサイズが700未満ならdefaultViewの書き換え
        if ($(window).width() < 700) {
            options.defaultView = 'listYear'; 
        };

  // カレンダーのレンダリング
        var calendar = new FullCalendar.Calendar(calendarEl, options);
        calendar.render();
    });
</script>

実装してみて

最初はツールチップで詳細予定出すか?などを考えていたものの、結局スマホで月間カレンダーの中身を見るのには限界があるので、リスト表示にしてしまうのが良かろうという結論に。

Google Calendarだってそうしてるんだし…でもTimeTreeは月間カレンダーなんだよなぁ。

参考文献

javascript - ビューポートの幅に基づいてfullCalendarビューとヘッダーオプションを変更しますか?

Documentation | FullCalendar

今回はstackoverrunがかなり参考になった…。

DjangoでGoogle Calendarへアクセス②

こんなことがしたかった

  • 定期的にGoogle Calendarに予定を取りに行きDBにデータを格納したかった

厳密にはもう少し複雑ではあるのだが、Djangoで作った予定管理アプリのようなものに予定を追加したときに、Google Calendarにも同期したかったのである。当記事はGoogle CalendarDjangoの方を書く

GASでイベント時にトリガー追加して、Django側にはAPIを準備してPOSTしてもよかったのだが、今回はDjango側でGoogle Calendarにアクセスしイベントを取りに行くパターンで対応する。

こんな感じ

Djangoのカスタムコマンドを準備し、python manage.py 〜で実行できるようにし、crontabに登録して定期実行することとした。

カスタムコマンドの準備

どうやら、自分のアプリケーション内にmanagementディレクトリを切り、その中で更にcommandsディレクトリを切り、その中にinit.pyとカスタムコマンド用のスクリプトをおけば良いようだ。

以下のような感じ

Project
    app
        management
            commands
                __init__.py
                customcommand.py

customcommand.pyとそれに絡む中身は以下のような感じ。ちなみに細かい要件は以下の通り

※例によって動かなかったらごめんなさい あと、実際に書いたプログラムを一部改変しているので、余計なimportがあるかもしれない

※settings.pyは先日と全く同じなため省略

  • customcommand.py
import datetime
import os.path
import requests
import httplib2

from django.conf import settings
from django.core.management.base import BaseCommand
from pytz import timezone
from app.models import Model
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
from google.auth.transport.requests import Request

class Command(BaseCommand):
    help = 'Google Calendar Import Events'

    # ここが本体
    def handle(self, *args, **kwargs):
        events = self.get_gcal()
        self.insert_db(events)
        self.stdout.write(self.style.SUCCESS('Successfully import Google Calendar'))

    # イベントを取ってくる
    def get_gcal(self):
        # 認証情報を作成
        credentials = ServiceAccountCredentials.from_json_keyfile_name(
            settings.GCAL_PRIVATE_KEY_PATH,
            scopes=settings.GCAL_SCOPES
        )
        http = credentials.authorize(httplib2.Http())
        service = build('calendar', 'v3', http=http)

        # 情報を取得
        now = datetime.datetime.utcnow().isoformat() + 'Z'
        events_result = service.events().list(calendarId=settings.GCAL_CALENDAR_ID, timeMin=now, \
                                        singleEvents=True, orderBy='startTime').execute()
        events = events_result.get('items', [])
        return events

    # DBにデータを入れる
    def insert_db(self,events):
        now = datetime.datetime.now()
        # DB内の情報を取得
        obj = Model.objects.filter(enddt__gte=now)

        for event in events:
            name = event.get("summary")
            startdt = event.get("start").get("dateTime")
            enddt = event.get("end").get("dateTime")
            gcal_eventid = event.get("id")

            # すでにDBに登録されている場合は対象外
            if obj.filter(gcal_eventid=gcal_eventid).exists():
                pass
            # 終日予定は取り込み対象外
            elif startdt is None or enddt is None:
                pass
            # 上記以外は取り込み
            else:
                Model(name=name,startdt=startdt,enddt=enddt,gcal_eventid=gcal_eventid).save()
  • models.py
from django.db import models

class Model(models.Model):
    class Meta:
        db_table = 'model'
        app_label = 'model'

    name = models.CharField(
        verbose_name = '名前',
        blank = False,
        max_length = 64,
    )

    startdt = models.DateTimeField(
        verbose_name = '開始日',
        blank = False,
    )

    enddt = models.DateTimeField(
        verbose_name = '終了日',
        blank = False,
    )

    gcal_eventid = models.CharField(
        verbose_name = 'google calendar eventid',
        blank = True,
        max_length = 256,
        null = True,
    )

これでpython manage.py customcommandを実行すれば、DBにGoogle Calendarのイベントを取り込める。

ちなみに、実行する際はcrontabで1分ごとに動かしている。

crontabの設定

詳細は省くが、1点のみちょっとだけハマったところが。

当然ではあるが、venv環境だとcrontabで単純にpython manage.py customcommandを書いても動かない。

上記のほか、cdとactivate、deactivateを使用し、venv上で実行できるように組む必要がある。

実装してみて

今回は単純に取り込む形を書いたが、予定を移動した場合もDB側に反映させる必要があるので、

Update的な処理をバッチ内に入れる必要があるように思う。

また、今回は1分ごとの実行としたが、リアルタイム性を求めるのであれば別の方法を考える必要があるかもしれない。

参考文献

[Django]カスタムコマンドでバッチ処理をする - Qiita

Django でもバッチ処理がしたい! - Qiita

【Django】カスタムコマンドを使用しcron(crontab)で登録・削除を自動化

DjangoでGoogle Calendarへアクセス①

こんなことがしたかった

  • CreateViewでDBにデータがInsertされたときに、Google Calendarにもイベントを登録したかった

厳密にはもう少し複雑ではあるのだが、Djangoで作った予定管理アプリのようなものに予定を追加したときに、Google Calendarにも同期したかったのである。

最終的には双方向で同期するようにしたいのではあるが、とりあえずはDjangoアプリ→Google Calendarの方。

こんな感じ

前提として、Google Cloud ConsoleからGoogle Calendar APIを有効化し、サービスアカウントを作成しておく。

かつ、そのサービスアカウントにGoogle Calendarの予定の変更権限を渡しておく。

また、pipで必要な以下のライブラリも入れておく。

github.com

github.com

プログラムは以下。※動かなかったらごめんなさい

  • Views.py
import requests
import json
import os.path
import httplib2

from django.conf import settings
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
from google.auth.transport.requests import Request

from app.forms import Form
from app.models import Model

class CreateView(CreateView):
    model = Model
    form_class = Form
    template_name = 'create.html'

def form_valid(self, form):
        obj = form.save(commit=False)

  # Google Calendarに追加する予定のデータを作成
        gcal_ctx={
                'summary':'タイトル',
                'start':{
                    'dateTime':予定の開始日時,
                    'timeZone':'UTC'
                },
                'end':{
                    'dateTime':予定の終了日時,
                    'timeZone':'UTC'
                },
            }
            # Google Calendarに予定データを渡してInsert、返り値のeventidを格納
            gcal_eventid = self.insertgcal(gcal_ctx)
            
            # Google CalendarのイベントIDを記録
            obj.gcal_eventid = gcal_eventid
            obj.save()

        return redirect('処理完了画面')

# Google CalendarにInsertする関数
def insertgcal(self, body):
        # 認証情報を作成
        credentials = ServiceAccountCredentials.from_json_keyfile_name(
            settings.GCAL_PRIVATE_KEY_PATH,
            scopes=settings.GCAL_SCOPES
        )
        http = credentials.authorize(httplib2.Http())
        service = build('calendar', 'v3', http=http)

        event = service.events().insert(calendarId=settings.GCAL_CALENDAR_ID, body=body).execute()
        # idを返してDBにidを入れる
        return event.get("id")
  • settings.py
〜前略〜
# GCAL
GCAL_PRIVATE_KEY_PATH = 'サービスアカウントのjson'
GCAL_SCOPES = ['https://www.googleapis.com/auth/calendar']
GCAL_CALENDAR_ID = 'Google CalendarのID'
  • その他 アプリケーションのルートに「サービスアカウントのjson」ファイルを配置しておくこと。

ようするに、InsertしたデータからGoogle Calendarのイベントを作り、認証をとおして、Google側が準備した「service.events().insert」を使ってカレンダー側にも反映させるのであった。

また、Google Calendar側のIDを取得しておき、Django側にも持たせておかないと、Google CalendarDjango側の取得のときに面倒なことになるので、そういった処理も入れている。

実装してみて

実装自体はそれほど難しくなかったのだが、Google APIの認証がちょっと厄介だった。

最初はブラウザでの認証が必要な別の方式でやっていたが、サーバに載せたときにサービスアカウント方式でないと詰まることがわかり急遽認証方式を変更したのが主なトラブル。

何に使うにしても汎用性は高そうな気もする。

参考文献

個人のGoogleカレンダーの予定をPythonで取得する | ヤマムギ

オフィスへの来訪申請をGoogleカレンダーと連携させて自動化してみたよ|Holidayおでかけラボ|note

【Python】Google Calendar APIを使ってGoogle Calendarの予定を取得・追加する | 無次元日記

Events  |  Calendar API  |  Google Developers

PCのデスク周りを改善し続けていた自粛期間であった

在宅勤務にかこつけて…

例にもれずうちの会社も4月〜5月末は在宅勤務だったので、その2ヶ月の間にデスク周りを色々と改造した。

そうしたらいつの間にか要塞みたいになっていたので、買い足したものに関しての所感をメモ書き。

こんなものを買った

いろいろ買ったので紹介。Amazonリンクを貼ってはいるものの、めんどくさいかつ収益が見込めないのでアソシエイトの設定はしていない。

LG 29UM69G-B

29インチウルトラワイドモニター(の中でも安いモデル)。

会社では同サイズのモニターで仕事をしているが、家は24インチ1枚だったので購入。

単純に横に広いのでコーディングや資料作成はやりやすい、USB typeCで出力できるところもポイントが高い。

サブ機となってしまったMacbook Pro(2017)もストレスフリーでつなげるようになったほか、在宅勤務のため会社から支給されたPCを合わせて3台を使うことになっているため、切り替えるのが非常に楽になった。

HDMIスイッチャー

メイン機(デスクトップ)と在宅勤務機(ノート)を両方HDMIで繋ぐことになったので、差し替えがとてもだるくなり購入。

複数台PCを使い、かつ同じケーブルでつなぐ場合は持っていて損はないと思う。

DP買っても良かったかな…とは今は思っているが…

アーム型ブックスタンド

これ、今回買った中でもかなり良い商品。ブックスタンドにアームが付いているだけの代物ではあるものの、コーディングなどする際に狭い机でも本を開きっぱなしにできるのが最強すぎて。

なおかつ、それほど目線を落とさず本の内容を見ながらタイピングできる。

ここ2ヶ月は今まで使っていた言語とは異なる言語でコーディングしているため、本で調べながら書くケースが多いのだが、段違いに効率が上がった。

本当に買ってよかった。本見ながらPC使う全人類が買ったらいいと思う。

※ただし見た目はちょっとチープ…だしパーツの精度もそんな高くない…。

小物収納箱

机周りの小物を片付けたく購入。チープさはあまりなく、容量も十分。

そこが深い引き出しもついているため、ペンやメモ帳のストック、ケーブル類、薬、腕時計、マウス、変換ケーブル、電卓あたりの使用頻度が高めのモノを入れておける。

ヘッドセット

ヘッドセットは1回買い直した。最初はBluetoothで接続するブランドもよくわからないやつを使っており、何も使わないよりは断然良かったのだが、時々ノイズがすごかったり、会社用PC・MaciPhone全部で何かしら通話するケースがあったため、ペアリングがごちゃごちゃとしてしまった(切り替え機能も見当たらず)

そのあたりのストレスが強かったため、別途ゲーミングヘッドセットを購入。

こちらは有線4極(3極変換コネクタ付)なので、デバイスごとの切り替えは問題ない。刺すだけ。音質も問題ない。

ただ、飲み物取りに行ったりなどで離席できないという弱点があるので、集中して会話したいときや仕事のときは有線、プライベートのときは無線、みたいな使い分けでも良いかなと思っている次第(基本有線で事足りるけど)

ディスプレイ台

ディスプレイ自体はアームで使っているのだけれども、机を拡張したくて購入。

奥行き60cmの机を使っているので、机の上にMacをおいているとキーボードとリストレストをおいた時点で他のものが置けなくなってしまうので買ってみたところ、思った以上にスペースが広がった。

さらに、キーボードを収納するスペースができたため、ノートや本を広げるスペースも作ることに成功。

冷蔵庫をヨドバシで買ったときのあまりポイントで手に入れたのだが、選んで正解だったように思う。

総額で5〜6万くらい行ってしまった(自作PCも含めると15万弱…?)ような気もするが、机に向かうモチベーションが爆上がりしたのでよしとする。明日から通常出社だけど…。

HTML5プロフェッショナル認定試験レベル1を取った話と資格についての個人的な考え

HTML5プロフェッショナル認定試験レベル1を取得したので、個人的な感想などを書く。

もともとのレベル

  • IT系(Web寄りの仕事をしているが、SIっぽい部分もある)の中小で、受託開発業務のSE兼社内システムのPG兼情シス業務をしている(3年目くらい)
  • 前職は常用型派遣のインフラ系エンジニア(仮想サーバ・セキュリティの仕事)を3年間
  • 応用情報/登録セキスペ(未登録) には合格している

というわけで、IT全般としては多少は知識があった状態。

取った理由

これはいくつかあって、一番大きい理由としては後輩社員への挑発

うちの会社は未経験採用も多く、メンターのようなこともする機会があるのだが、前職の影響もあり社内でも資格ホルダーな方なので、よく「なんの資格取ったらいいですかね」と聞かれることがある。

その都度「取るなら基本情報でも応用情報でもとっとけば、HTML5のやつでもいいんじゃない?」と適当に答えていたのだが、知識を得たいと思っている割には誰も取る気配はないし、業務中の質問に答えたり、ちょっと社内勉強会で僕でも話せるような技術の基礎話をしても「初めて知りました!」みたいな反応が多かった。 (かなりフランクな会社なので、忖度とかアピール的なことで言っている感じでもない)

業務時間外の勉強などはどっちでもいいかなというスタンスなので、あまり強制力は持たせたくないのだが、口ぶりの割には…と思ってしまうので、先輩社員が資格を取って刺激にでもなればなぁと、サクッと取れる資格を選んだ。

他の理由としては、インフラ・サーバサイドあたりの知識や経験は仕事の中で溜まっていくけれども、フロントの知識は溜まりづらく、プライベートの開発がどうしてもスピード感が出ず、少しかじっといてもいいのかなと思ったので。

ちなみに、報奨金は合格時に5万程度なので、一発合格でプラスにはなるが、もらえたら嬉しいなぁ程度。

得点

  • トータル:86点 (合格ライン:70点)
  • Webの基礎知識:92%
  • CSS:76%
  • 要素:88%
  • レスポンシブWebデザイン:100%
  • APIの基礎知識:80%

レスポンシブWebデザインが100%取れたのは素直に嬉しい。その反面、Webの基礎知識が100%取れなかったのと、CSSが雑魚すぎるのがちょっと悔しい。

勉強法

ある程度の知識はあると思っていたので、以下の問題集を流してみた。

だけれども、思ったより全然解けない。初見で合格ラインに達したのはWebの基礎知識くらいで、CSS→要素→レスポンシブWebデザイン→APIと進むにつれてほぼ解けなくなっていく。

そのため、知らない内容はググりつつ、Ping-tをスキマ時間に細々実施。

やりながら、「受かるために覚える事項」と「今後のために覚えておく事項」に切り分けながら行うことにした。

例えば、以下の内容は今の自分の環境だとそんな使わないだろうしある程度割り切ることにした。

  • HTML5から廃止になったタグ(使わないから…)
  • 動画関連(Youtube埋め込みをまずは考えるのではなかろうか)
  • CSSの細かいプロパティ(BootStrapとかのCSSフレームワークを使うことが多い)

その反面、以下の内容は丁寧に勉強した。

  • 要素の考え方(コンテンツモデルやセクション、アウトラインなどの考え方は知っておくべきかと判断した)
  • API(全く知らなかったので、今後JSを使うときに関わってきそうだと判断した)
  • レスポンシブデザイン全般(今個人開発しているWebアプリで一番困った箇所だったので)

そんなこんなで、1ヶ月ほど合間時間を使って、最後の2日はスパートかけて、計20時間程度での合格。

取ってみての感想

思ったよりも大変だった。知ってるつもりの箇所が非常に多かった。

一番身近なHTML/CSSも奥が深く、無知であったことを知れたのがプラスかと思う。

受託開発時にWebデザイナーさんから納品のあったHTML/CSSを確認したりすることもあるので、実務面でもある程度は役に立ちそう。

ただ、これを受けたからフロントができるかといえば難しいだろうなとも思う。多少は調べるときの吸収率も良くなりそうではあるが。

資格を取ることについて

正直、転職だったりキャリアアップに対しては直接の影響は無いと思うし資格を取ったからそれができるようにはならない。それを主目的にするのは効率が悪い。

ただ、周辺知識を含めた体系的な知識はある程度つくし、半分以上忘れてしまっても次に調べたときの理解が早くなるなどのメリットもあるので、一概に無駄とも言いきれない。

実際に諸々構築したりしたほうが経験と知識がつけられるだろうが、資格試験を通しての勉強は勘所みたいなものは得られるのではないかなとも思う。

あと、ある程度全般的な知識は無いと、顧客と打ち合わせしたときに結構辛いかもしれない。「僕インフラ触ったこと無いんでTLSとか知りません」というわけにもいかないだろうし…。

何を土俵にするエンジニアによるけれども、IPAの試験については何か1つは取っておいたほうがいいと思う。

ただ、ベンダー試験は要注意で、自分に必要そうだなーと思うものをある程度選定する必要はある。特定のカテゴリに絞った試験になるので。

フロントエンジニアがCCNAを取って役に立つか?といったら残念ながらあまり役に立たないケースが多い。

個人的な意見としては、実際の経験には敵わないので、上位資格やベンダー資格は必要に応じて取りたかったら取ればいいが、基本情報・応用情報くらいは持っておいてもいいんじゃない?という感じ。

得意なスキルを10とすると、それ以外の知識も2くらいはほしいし、それは資格で補ってもいいんじゃないかな?