情報自炊

情報を血肉にしたい

ソフトウェア要求(第1章, 第2章)

ソフトウェア要求 第3版

ソフトウェア要求 第3版

「第1章 ソフトウェア要求の基礎」と「第2章 顧客の観点から見た要求」を読んだのでメモ。

ソフトウェアの世界の問題の多くは、人が、プロダクトの要求について知り、文書化し、合意に達し、修正する方法に欠陥があるところに起因する

ソフトウェアはいまやビジネスに必要不可欠なものである。ということはビジネスに携わっている人間全員が、ソフトウェア開発に何らかの形で関わってくる。

ソフトウェア要求を満足の行くレベルで満たすには、登場人物それぞれの意見を共有することが大事であり、要求の理由を文書化するのはとても大事。

この章では「要求」という言葉を定義して、いろんな要求があるんだよということを主張している。

ソフトウェア要求には、ビジネス要求、ユーザー要求、機能要求という3つの異なるレベルがある。

言葉が多すぎて全然覚えられないのだが、大まかなレベルは上の通りなので、これは最低限覚えておくことにする。

経営者だったりマネージャーだったり顧客だったり…。それぞれの立場の人たちとのコミュニケーションが大事だと、耳タコな言葉が本書でも綴られている。だからそのコミュニケーションとやらをどうやって継続的に実現するんだよ、という疑問にはまだ本書は答えてくれていない。How to本では無さそうなので具体的なコミュニケーション術までは載っていないと思うけど…どうなんだろう。

RESUMEというサービスを使ってレジュメを作成しました

こちら。 www.resume.id

私の長期休暇中の目標のひとつとして「これまでの社会人生活を振り返って、次の生き方を考える」というものがあります。これはその第一歩。いろいろ棚卸しをしているところなのです。

今持っているアカウント類も、主なやつだけ載せておきます。

他にもいろいろあるような気がするけど、この辺の掃除も含めて明日取り組んでいきます。

Vue.js&Nuxt.jsでアプリ作成したときにハマったところまとめ

前回の続き。 hoshitostar.hatenablog.jp

ノート。 f:id:hoshitostar:20190502005413j:plain

ディレクトリ構成。初めて扱うフレームワークだったので何をどこに置いたらいいのか、あのファイルどこにいったっけと探すのが大変だった。 f:id:hoshitostar:20190502005558p:plain

nuxt.config.jsはいろいろ書いたので全部のせておく。

import pkg from './package'
require('dotenv').config();
const {
  CONSUMER_KEY,
  CONSUMER_SECRET,
  ACCESS_TOKEN_KEY,
  ACCESS_TOKEN_SECRET
} = process.env;

export default {
  mode: 'spa',

  /*
  ** Headers of the page
  */
  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    script: [
      { src: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js" },
      { src: "https://unpkg.com/popper.js"},
      { src: "https://unpkg.com/tooltip.js"}
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"}
    ]
  },

  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },

  /*
  ** Global CSS
  */
  css: [
  ],

  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    { src: '~/plugins/calendar.js', ssr: false }
  ],

  /*
  ** Nuxt.js modules
  */
  modules: [
    '@nuxtjs/dotenv',
  ],

  /*
  ** Build configuration
  */
  build: {
    /*
    ** You can extend webpack config here
    */
    extend(config, ctx) {
      config.node = {
        fs: "empty" // TwitterAPIを使うのに必要
      };
    }
  },
  serverMiddleware: [
    '~/server'
  ],
  env : {
    CONSUMER_KEY,
    CONSUMER_SECRET,
    ACCESS_TOKEN_KEY,
    ACCESS_TOKEN_SECRET
  }
}

躓いたところを一つずつ記載。

環境変数を使う

  • npm install --save @nuxtjs/dotenv
  • nuxt.config.jsに情報を記載
    • dotenvとかenvとか書かれているところ
    • この設定だけでけっこう散らばって記載しているけどこれがベストなのかは疑問
  • .envファイルに環境変数を記載(このファイルはコミットしない)
    • 下のような感じ
CONSUMER_KEY="xxx"
CONSUMER_SECRET="xxx"
ACCESS_TOKEN_KEY="xxx"
ACCESS_TOKEN_SECRET="xxx"

サーバ起動前に環境変数を設定する方法もありそうだったが、毎回設定するよりは外部ファイルに置いておく方が好みなのでこの方法を取った。

localStorage, sessionStorageを使用する際はSPAモードで実行

これは書籍にもあった。SPAとSSRの違いがよく分かっていないので(サーバレンダリングの有無だけ?)もう少し勉強する必要がある。

今回はsessionStorageにTwitterトークン情報を保存している。

developer.twitter.com

We would not normally recommend using LocalStorage, although there may be a few exceptions to this.

Using SessionStorage is normally more preferable than LocalStorage when thinking in terms of security.

Twitterのdevelopページにこう書かれていたので、localStorageではなくsessionStorageを使った。

TwitterAPIを使用するにあたって

  • npm install --save twitter
  • npm install --save axios fs net tls(依存するライブラリたち)

fsが使えずに下記のようなエラーが出た。

ERROR in ./node_modules/request/lib/har.js Module not found: Error: Can't resolve 'fs' in '/Users/hoshito/RubymineProjects/tenreki/node_modules/request/lib'

fsはファイルシステムのことで、SPAつまりクライアント側でファイルシステムモジュールを使おうとしてエラーになっていた。

nuxt.config.jsに下のように書くとエラーを回避できた。

    extend(config, ctx) {
      config.node = {
        fs: "empty" // TwitterAPIを使うのに必要
      };
    }

SPAだとこれが動かずSSRだとこれが動かず、というのが多く混乱してしまう。

node-sassは(今だけ?)使えない

前回の記事に書いた通り。

ビジネスロジック

serverMiddlewareを使った。 ja.nuxtjs.org

page/list.vueから自身にAPIを叩くように使っている。

page/list.vueの一部

    methods: {
      getTweets(token, secret) {
        const self = this;
        const params = {token: token, secret: secret};
        axios.post("/api/tweets", params).then(res => {
          self.events = res.data;
        }).catch(err => {
          console.log(err);
        });
      },

server/index.jsの全部

import express from 'express'
import bodyParser from 'body-parser'
import {getClient, getTweetInfoForFullCalendar} from '../plugins/twitter'

const app = express()
// requestでjsonを扱えるように設定
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())

app.post('/tweets', (req, res) => {
  let client = getClient(req.body.token, req.body.secret);
  // TODO: リクエストパラメータに入れる
  let params = {owner_screen_name: "hoshitostar", slug: "Love", count: 200};
  client.get('lists/statuses', params).then(tweets => {
    let result = getTweetInfoForFullCalendar(tweets);
    res.send(result);
  }).catch((errors) => {
    // TODO: error処理
    console.log(errors);
  });
})

module.exports = {
  path: '/api',
  handler: app
}

これも最初全然リクエストを投げられず、投げることができたと思ったらリクエストパラメータ取れなくて苦労した。

server/index.jsRailsでいう(別にRailsじゃなくてもいいけど)Controllerの役割を担っていると思ったので、ロジックはplugins/twitterに入れた。本当はclient.get...の部分も含めて外に出そうと思ったけど、client.getの非同期処理でハマってしまったのでやめにした。

plugins/twitterの一部

// TwitterAPIのクライアントクラスを取得
function getClient(token, secret) {
  const Twitter = require('twitter');
  return new Twitter({
    consumer_key: process.env.CONSUMER_KEY,
    consumer_secret: process.env.CONSUMER_SECRET,
    access_token_key: token,
    access_token_secret: secret
  });
}

// --- 省略 ---

export {getClient, getTweetInfoForFullCalendar}

ハマったところは以上になる。

Webアプリで必要なところの大まかな部分を実装できたと思うので概ね満足。データ保存はsessionStorageを使っているので、本格的なDBを使うところが足りていないか。「Vue.js&Nuxt.js超入門」にはFirebaseを使ったデータのやり取りが書いてあったのでこれを使えばいけるはず。もう少し本格的にやるならサーバサイドはNuxt.jsではなくRailsにしてもいいかもしれない。Rails + Vueってどうやるのかが今後の課題になるであろう。

ちょっとしたWebアプリであれば全部JavaScriptで組むことができると分かったのが今回の大きな収穫。クラウドAPIサービスを駆使すればほぼサーバレスで組むこともできそう。

Vue.js&Nuxt.jsでアプリ作成

このようなアプリを作りました。 f:id:hoshitostar:20190501171617p:plain 「囀暦」で「てんれき」と読みます。Twitterでつぶやかれている内容の中で「○月○日にxxします!」という告知をカレンダー上にプロットするものです。以前にもRailsでちょこっと作っていたのですが、今回「Vue.js&Nuxt.js超入門」を勉強したので改めて作り直してみた次第です。

教科書通り作るのとはわけが違う。エラー&エラーに悩まされました。ので今日と明日で学んだことをまとめていきたいと思います。(今日はちょっとだけしか書いてないです)

構成

  • Vue.js(フロント)
  • Nuxt.js(バック)
  • Firebase(認証)

また、ライブラリとして下記を使っています。

初期設定

$ npx create-nuxt-app tenreki
> Generating Nuxt.js project in /Users/hoshito/RubymineProjects/tenreki
? Project name tenreki
? Project description My primo Nuxt.js project
? Use a custom server framework none
? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Use a custom UI framework none
? Use a custom test framework none
? Choose rendering mode Universal
? Author name hoshito
? Choose a package manager npm

明示的にinstallしたもの。

npm install --save twitter
npm install --save axios fs net tls
npm install --save @nuxtjs/dotenv
npm install firebase --save
npm install --save @fullcalendar/vue @fullcalendar/core @fullcalendar/daygrid

sassを使いたくてnpm install node-sassをしたところ脆弱性エラーで進めなかった。調べてみると最近出たエラーっぽい。

解決策はいろいろ出てきている。 github.com zatsuzen.com

package.jsonnode-gypを書き換えろ、という内容であるが、私のpackage.jsonは以下のようなものであり、どこをどう書き換えればよいのか分からずsassの導入を断念しました。

{
  "name": "tenreki",
  "version": "1.0.0",
  "description": "My primo Nuxt.js project",
  "author": "hoshito",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
  "dependencies": {
    "@fullcalendar/core": "^4.1.0",
    "@fullcalendar/daygrid": "^4.1.0",
    "@fullcalendar/vue": "^4.1.0",
    "@nuxtjs/dotenv": "^1.3.0",
    "axios": "^0.18.0",
    "cross-env": "^5.2.0",
    "firebase": "^5.10.1",
    "fs": "0.0.1-security",
    "net": "^1.0.2",
    "nuxt": "^2.4.0",
    "sass": "^1.19.0",
    "tar": "^4.4.8",
    "tls": "0.0.1",
    "twitter": "^1.7.1"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

UdemyでユニティちゃんのUnity講座を受講

初めてUdemyを使った。

ユニティちゃんが教える!初心者向けUnity講座 Online Courses - Anytime, Anywhere | Udemy

受講料は無料。さらに受講時間も全体で2時間半とかなり気軽に取り組める。できあがるゲームも面白い。

以前からAR/VR関係には興味があったが、Unityを使っていることが多い…らしい。

さてこれを受講した後、どう学習していけばいいのか。悩みどころである。

GW期間、読みたい!勉強したい!と思わせるコンテンツが多かったのでまとめてみる

書籍

Kindle

50%OFF

ソフトウェア要求 第3版

ソフトウェア要求 第3版

こっちも50%OFF

この本は原著、翻訳本の2つが存在。原著のみ50%OFF。

この記事で原著が絶賛されていた。 www.jabba.cloud

BOOTH

booth.pm

booth.pm

その他

peaks.cc

学習

tutorials.chainer.org

www.udemy.com

1kohei1.com

転職サービス

jobs.forkwell.com

こういうの、メモしただけ買っただけでやらなかったり、無計画に手を出して時間を浪費するってのがあるあるなので気をつけたい。

AtCoder Beginner Contest 125に挑戦

結果。 f:id:hoshitostar:20190427231254p:plain

前回はコンテスト初挑戦だったということもあり、楽しいという感情よりも焦りの方が強くでいていたが、今回はだいぶ楽しんで望むことができた。

問題A、Bを8分で解き、残り92分を問題Cに費やした。問題Dは最初から解くつもりがなかったのでスルー(本来の試験であれば問題Dにも目を通しておくべきだが)。

問題Cは条件付きの最大公約数を求める問題。 atcoder.jp

私の最初の解答はこれだった。

n = gets.chomp.to_i
arr = gets.chomp.split(" ").map(&:to_i)

def create_yakusu_arr(num)
  return [1] if num == 1
  return (1..Math.sqrt(num).to_i).select {|i|
    num % i == 0
  }.map {|a| [a, num / a]}.flatten.uniq.sort
end

yakusu_arr = arr.map do |a|
  create_yakusu_arr(a)
end

max = 0
n.times do |i|
  p = yakusu_arr.select.with_index{|arr, j| i != j}.reduce(&:&)
  max = p.max if p.max > max
end

puts max

各数字に対して約数を求め、yakusu_arr.select.with_index{|arr, j| i != j}.reduce(&:&)にて共通部分を取る(公約数を求める)というもの。最後にmaxを取ることで最大公約数を出している。

i != jとあるように、選んだ整数は無視して良い(正確には、選んだ整数を、それ以外の整数の最大公約数と一致させるようにする)、ということには気づいていた。

たぶんこれはロジック的には正しいのであろう。結果はTLEだったので、このあとずっと高速化に努めていた。結果として最後までTLEだったのだが、提出するに連れてACの数が増えていったので興奮した。

左が最初の提出。右が最後の提出。 f:id:hoshitostar:20190427232858p:plain

これが競技プログラミングの醍醐味かと競技中は思ったが、いま振り返ってみると1, 2問しかACが増えないってことは、根本的にアルゴリズムを見直す必要があったということかと反省している。

最後に提出したコード

require "set"

n = gets.chomp.to_i
arr = gets.chomp.split(" ").map(&:to_i)

hash = {}
arr.each do |a|
  (1..Math.sqrt(a).to_i).each do |i|
    if a % i == 0 then
      hash[i] = (hash[i] || 0) + 1
      next if i == a / i
      b = a / i
      hash[b] = (hash[b] || 0) + 1
    end
  end
end

max = 1
hash.select do |k, v|
  if v >= n - 1 then
    max = k if k > max
  end
end

puts max

前のコードを修正して作ったので変なコードが紛れ込んでいる(require "set"は使っていないし、hash.selecthash.eachを意図して使っていた)

各整数で約数を求めるところは最初と変わらないが、最大公約数を求めるところに工夫を入れた。

  if v >= n - 1 then
    max = k if k > max
  end

カウントvがn-1個以上であれば、選んだ数字の書き換えによりv == nにすることができ、これは全整数の約数にkが含まれていることを意味する。

ちなみに最大公約数といえばユークリッドの互除法、ということには最初から気づいていたものの、計算量が分からなかったので最初は使っていなかった。

今回は解説を見てかなり納得感が言った。特に、競技中にも思ったが、前の結果を使ってうまく最大公約数を求められないだろうか、という疑問に答えていたのでスッキリできた。

解説を見て書いたコード。これはACで通ったのも確認済み。

n = gets.chomp.to_i
arr = gets.chomp.split(" ").map(&:to_i)

def gcd(a, b)
  if a < b then
    tmp = a
    a = b
    b = tmp
  end
  return a if b == 0
  r = a % b
  while r != 0 do
    a = b
    b = r
    r = a % b
  end
  return b
end

tmp_l = nil
tmp_r = nil
l_arr = []
r_arr = []
(n+1).times do |i|
  if i == 0 then
    l = 0
    r = 0
  else
    l = gcd(tmp_l, arr[i-1])
    r = gcd(tmp_r, arr[i * (-1)])
  end
  l_arr << l
  tmp_l = l
  r_arr << r
  tmp_r = r
end
r_arr.reverse!

max = 0
n.times do |i|
  m = gcd(l_arr[i], r_arr[i+1])
  max = m if m > max
end

puts max