こざテク

機械系エンジニア→Web系エンジニアを目指して勉強中

【超初心者向け】アジャイル開発、始めてみませんか?

この記事は「RUNTEQ Advent Calendar 2020( https://qiita.com/advent-calendar/2020/runteq )」のアドベントカレンダー17日目の記事として書いています。

あなたは何をしている人なんですか?

あ、どうも、こざくらです。
RUNTEQに6月に入学し、働きながら勉強して、晴れて11月末に卒業しました。やったー!
製造業界で機械を設計開発するお仕事をしているので、開発のお話には興味ある感じです。

アジャイル開発の話の前に

話はちょっと逸れますが、RUNTEQではDiscordというビデオチャットアプリでの交流が盛んです。
勉強部屋とか雑談部屋とか目的にあったいろんな部屋が設けられています。
生徒さんも講師さんも運営さんも、ここでワイワイガヤガヤしてます。 立場的なうんぬんは関係なく。
うん、素晴らしいコミュニティ力!最高!
(個人的にはDiscordでの交流があったから学習を続けられました)

RUNTEQ受講生の生の声をもっと知りたい方は、以下のレポート例をご参照ください。
yasunari-kainuma.com
wataru-pgm.hatenablog.com
naotolm10.hatenablog.com
note.com

っで、なんでアジャイルに興味あんの?

アジャイルスペシャリストに出会ったから

Discordで雑談をしていたら、アジャイルスペシャリストの方がひょこっと来られていろんな話をしてくださいました。
アジャイルのこと、スクラムのこと、ソフトウェア開発に関するあれこれ。
初対面&偉い方だったので、「あぁぁーこわいかなー」て身構えましたが、めちゃんこ優しい方でした。
1つ聞いたら10も20も教えてくださるgiveの精神。すごい。特にアジャイル開発にまつわる知識や情熱には圧倒されました。プロフェッショナルかっこいい。
こんな話が聞けるのもRUNTEQのコミュニティ力さまさまです。ありがとうございます。RUNTEQ生はどんどんDiscordに行って頂ければと。行くとなんか良いことがあると思います。ゲームとかしたりね。楽しいですよ。

アジャイル開発の成り立ちがおれ的に、ほぇー!!!!だったから

興味をもったもう1つの理由は、アジャイル開発の成り立ちです。

ぼく 「アジャイル開発って製造業にも使えるんですか?」
アジャイルスペシャリスト 「こざくらさん、アジャイル開発はトヨタ生産方式を参考にしているんですよ」

驚きでした。
現職の会社はThe・製造業で、トヨタ生産方式を真似た製造方式をとっています。
「かんばん」「カイゼン」「ジャストインタイム」などなど。そりゃあ天下のトヨタの真似するよね。
トヨタ生産方式とは | ホワイトボード型☆生産管理システムADAP

ただどちらかと言うと、工場ラインでのお話って感じで、開発業務とかオフィス業務にはうちの会社では展開されていません。たぶんみんな関係ないことってどこかで思ってるのかも。自分も含めて。
それがなんでキラキラ感のあるWeb業界でのソフトウェア開発に使われれるんやろーと。
そんなにすごいんか、アジャイル開発は!?と、興味がわいた次第なわけです。
トヨタ生産方式とアジャイル開発|小原和典/JQ/プロジェクトマネージャー|note

アジャイル開発を学んでみた

アジャイル開発っちゅうのはこうこうこうで、手法の1つとしてスクラムがあるんやで。ほんで逆にウォーターフォール開発ってのはこうなんやで。ほんでほんで」ってバシッと説明できたらいいんですが、ググったら出てくるレベルですし、そもそもアジャイル開発したことないです、、、
なので、オススメしてもらった登竜門的な書籍を読んでみました。

つまりこの記事は読書感想文です!!

読んだ本

カイゼン・ジャーニーという本です。有名らしいので読んだことある方もいるかもしれません。Amazonレビューは星4.3です。
主人公が業務改善を通して成長してくヒューマンストーリーです。エモいです。チーム開発をやりきったシーンとかちょっと泣けます。
www.amazon.co.jp

そもそもアジャイル開発って?スクラムって?

そもそもアジャイル開発とはどのような進め方か?ざっくりいうと以下のイメージです。

・ 関係者は目的の達成のためにお互いに協力し合いながら進める
・ 一度にまとめてではなく少しずつ作り、早い段階から実際に動作するもので評価を繰り返す
・ フィードバックを継続的に得ながら、作っているもの自体や計画を調整する

「設計」→「実装」→「テスト」→「アウトプット」って取組みを小さく何度も行います。なので、開発途中で当初思っていたものよりも良いアイデアが出てくれば、それを受け入れて作るものを変えていくことができちゃう。俊敏。どんどん計画が変わっていく。スタートアップて感じ。カイゼン・ジャーニーでもそんな場面がいくつも出てきます。

っで、アジャイル開発を実現させる手法の1つとしてスクラムというフレームワークがあります。カイゼン・ジャーニーでは、スクラムという開発手法で話が進みます。
スクラムについての印象的な説明がこれ。

スクラムでは過去の失敗から学びを得ることも、大切にしている。  
困難で複雑なプロジェクトには人との信頼関係が必要であり、お互いを尊敬し合うということが、仕事を進める上で重要であるとスクラムはうたっている。  
つまり、単に開発工程だけを管理するためのフレームワークではない

うーん、めっちゃ人間味あふれる手法。

f:id:Zakku44:20201217063412p:plain

スクラムのイメージ(カイゼン・ジャーニーより)

読んで印象に残ったこと

1. プロジェクトの理解とコミュニケーション、めっちゃ大事

「このチームの目的は〜〜ですよ 」とか「優先順位は〜〜ですよ」ってのはチーム全員の頭で考えないといけないってありました。上司が決めて降ってくるってことが今までだったので少しビックリ。
「プロジェクトのWhyとHowを明確にする」ための取組みをインセプションデッキと言います。
とくに大事なのは以下の4つ。

<Whyを明らかにする問い>
・ われわれはなぜここにいるのか:プロジェクトのミッションはなにか?
・ やらないことリスト:スコープの設定
<Howを明らかにする問い>
・ 夜も眠れない問題:不安やリスクはなに?
・ トレードオフスライダー:優先順位は?(品質、コスト、ローンチというQCDの観点から)

とくに「プロジェクトのミッションはなにか?」ってのは度々出てきます。
開発期間が伸びると、メンバーが変わったり、モチベーションも低下したりしがちなので、立ち上げ時にこういうワーキングは有効やなーと思いました。

f:id:Zakku44:20201217075327p:plain
インセプションデッキ

2. 良い関係を作る手法も備わっている

トヨタ生産方式での手法(かんばん、なぜを5回繰り返して真因にたどり着く、ジャストインタイム)は、開発や製造の効率アップのための取組みです。
スクラムでは、そういう効率アップの手法はもちろんだけど、チームが良い関係を作れるような取組みも組み込まれていました。それが新鮮でおもしろかったです。
例えば、ドラッカー風エクササイズというチームビルディング。メンバー間で以下の質問をして、お互いの期待、思っていることを明らかにするって手法です。

①自分は何が得意なのか?
②自分はどうやって貢献するつもりか?
③自分が大切に思う価値は何か?
④チームメンバーは自分にどんな成果を期待していると思う
⑤その期待は合っているか?

チームに新しいメンバーが入ったときにお互いを理解したり、時間がたてばメンバーの状況(できることできないこと)も変わります。そういうミスマッチを防ぐのに効果的とのこと。素晴らしい。

3. ユーザーストーリーマッピングとMVP(RUNTEQで習ったぞ!)

MVPとは、「ユーザーにとって価値があり、かつ最小限の機能性を持った製品」のことです。
そう、RUNTEQのポートフォリオを考える会とかでよく出てくるやつです。
RUNTEQで習ったことが出てきたーやっぱ大事なんやーってことで印象に残ってます。

ユーザーストーリーマッピングは、「時間の流れに沿ってユーザーの行動を洗い出し、左から右にその変遷を可視化していくワーク」です。
簡単!楽しい!5分でわかるユーザーストーリーマッピング(User Story Mapping) - Qiita

ちなみに本では、開発終盤に大きな方向転換が必要となるシーンがありました。
時間がない中でどうすんの?もう無理じゃね?ってときに、ユーザーストーリーマッピングによる分析でMVPを考え直す、って使われてます。

・・・おれもなんかしたい、と思った方

ぜひやりましょう!

カイゼン・ジャーニーは、

第1部 一人から始める
第2部 チームで強くなる
第3部 みんなを巻き込む

の3部構成です。
1部では、主人公がひとり朝会で1日の計画を立てたり、タスクボードでタスクの見える化をしたりと、一人でコツコツ頑張ります。それが周りに影響して、1人から2人になり、チームになるって感じで話が進みます。 なのでまずは1人から!

最後に

ということで、こういう記事を書いたので自分も未来のアジャイル開発に備えて、1人カイゼンを初めてみようと思います。
まずはタスクボードでも買って部屋に貼ってみようかな。続くかな。。
サボってないかたまにチェックしてくれたらありがたいです(早速)

読んでいただきありがとうございました。

FactoryBotのbuildとcreate

まいど、りゅういちです。
今日は勉強しているRSpecで使うFactoryBotの復習です。

buildメソッドとcreateメソッド

Everyday Rails - RSpecによるRailsテスト入門には以下のような説明がありました。

次のことを覚えてください。
FactoryBot.build を使うと新しいテストオブジェクトをメモリ内に 保存します。FactoryBot.create を使うとアプリケーションのテスト用データベースにオブジェ クトを永続化します。(P.55)

他にも。

可能な限り FactoryBot.create よりも FactoryBot.build を使ってください。こうすればテストデータベ ースにデータを追加する回数が減るので、パフォーマンス面のコストを削減できます。(P.68)

buildとcreatetの違いや使い分けは以下のようになります。

build

メモリ上にインスタンスが作られます。 'build'メソッドで呼び出されたときにインスタンスが生成され、テストを抜けるとインスタンスは消滅します(下図がわかりやすい)

f:id:Zakku44:20201022013709p:plain
buildでメモリ上にインスタンスが作られるとは

コード例はこんな感じ。
spec/factories/tasks.rbはこれ。

FactoryBot.define do
  factory :task do
    title { 'Task' }
    content { "content" }
    deadline { Random.rand(from..to) }
  end
end

っで、spec/system/task_spec.rbはこれ。

require 'rails_helper'

RSpec.describe Task, type: :system do
  describe 'Task一覧' do
    context '正常系' do 
      it "一覧ページでTaskが表示されること" do
        task = build(:task)  #---①
        visit tasks_path
        expect(current_path).to tasks_path
        expect(page).to have_content task.title
        expect(page).to have_content task.content
      end
    end
  # 以下にテストコードが続く

①のtask = build(:task)it "is valid with all attributes" do〜〜end内で定義されています。buildではDBに保存されず、テストを抜けるとメモリ上からインスタンスが消滅することになるので、別のテスト(it "hgehoge"~)ではtaskの読み込みはできません。
DBに格納されていないことは、コンソール上でcreated_atupdated_atnilになっていることから確認できます。

[1] pry(#<RSpec::ExampleGroups::Task::Task::Nested>)> task = build(:task)
=> #<Task:0x00007f9aaf093318
 id: nil,
 title: "Task",
 status: "todo",
 deadline: Thu, 26 Sep 2019 00:00:00 JST +09:00,
 created_at: nil,
 updated_at: nil>

したがって、上記で挙げたようなDBアクセスが必要ないテストでは`create'ではなく'build'メソッドを使います。
というのも、課題でやるぐらいのテスト量では問題にならないかもしれませんが、実務ではテスト処理のパフォーマンスも求められるようなので、負荷を軽くする意識が必要みたいです。

create

DB上にインスタンスを作成します(インスタンスの永続化ってやつ)
したがって、DBにアクセスしてデータを取ってくる必要がある場合(データの個数、削除、カラムのユニークに対するテストなど)はcreateを使います。
コード例はこんな感じ。

describe 'Task削除' do
  context '正常系' do
    it 'Taskが削除されること' do
      task = create(:task)  #---②
      visit tasks_path
      click_link 'Destroy'
      page.driver.browser.switch_to.alert.accept
      expect(current_path).to eq tasks_path
      expect(current_path).to have_content('タスクが削除されました')
      expect(current_path).not_to have_content task.title
    end
  end
end

②でtask = create(:task)でDBにtaskが格納されています。
そのためtasks_pathにアクセスすると、taskに対応したclick_link 'Destroy'が出現します。
createではなくbuildtaskを定義した場合には、click_link 'Destroy'が出現しないため、ここでエラーが起こります。
コンソールで確認すると、DBに格納されていることが分かります。

[2]pry(#<RSpec::ExampleGroups::Task::Task::Nested>)> task = create(:task)
=> #<Task:0x00007f9ab6275620
 id: 1,
 title: "Task",
 status: "todo",
 deadline: Tue, 27 Aug 2019 00:00:00 JST +09:00,
 created_at: Tue, 27 Oct 2020 08:14:50 JST +09:00,
 updated_at: Tue, 27 Oct 2020 08:14:50 JST +09:00>

参考文献

FactoryBotにおけるcreateとbuildの違い
Relish

まとめ

RSpecでもコンソールが使って都度確認すると理解度がめっちゃアップしました
letlet!beforeの挙動の違い、「遅延評価とは?」もコンソールを使って確認してる感じで書かこうと思います。

Pythonで電圧波形のノイズキャンセリングからのFFT分析をしてみた(業務改善)

どうも、こざくらです。
今日はPythonを使った業務改善メモです。

背景

回転体に取り付けたセンサーから出力電圧をデータ処理をする機会がありました。

出力電圧には、いろいろなノイズ(電源周波数とか高次成分などなど)が乗っちゃってるので、ノイズ処理が必要です。
また、機械は回転体なので、n次成分に対しても適切な処理をしないといけません。

これらの処理を汎用ソフトでこなしていた(というか人に頼んでいた)のですが、時間がかかるし、何より処理がブラックボックス化されて自分で出来ない&理解できてない状態が不満だったので、勉強も兼ねてPythonで実装してみました。

コード内容

生データの取得

モジュールのインポートからデータの準備は以下の通り。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import os

# データのパラメータ
sec = 10             # 測定秒数 
dt = 0.0001          # サンプリング間隔
N = int(sec / dt)         # サンプル数
rps = 120
# 1, 2次周波数の±20Hzさせたら、漏れなくカットオフできそう
fc1 = rps - 20  # カットオフ周波数1[Hz]
fc2 = rps * 2 + 20  # カットオフ周波数2(1・2次BPF)[Hz]
t = np.arange(0, N*dt, dt)  # 時間軸
freq = np.linspace(0, 1/dt, N)  # 周波数軸

# CSVのロード
filepath = r"C:\Users\hogehoge"
filename = os.path.basename(filepath)  # 拡張子を含むファイル名部分の文字列
dataname = os.path.splitext(os.path.basename(filepath))[0]  # 拡張子を除くファイル名部分の文字列

df = pd.read_csv(filepath, encoding="UTF-8", skiprows=9)

# 保存フォルダ作成
save_dir = os.path.splitext(os.path.basename(filepath))[0]
if not os.path.exists(save_dir):
    os.mkdir(save_dir)
ポイント

np.arange()
引数はnp.arange(start, stop, step)となり、等差数列を配列ndarrayとして生成する。
np.linspace()
np.arange()と同様に等差数列を生成するけど、間隔(step)ではなく、np.linespace(start, stop, num)のように要素数を指定する。
os.path.splitext()
ファイル名やフォルダ名から拡張子(.txtとか)を取得するメソッド。
そのため、ピリオドはパス名の1番右で分割する。下記が実行例。

filename = "/python/python.txt"
ex = os.path.splitext(filename)

print(ex)
print(ex[0])
print(ex[1])

# 実行結果
('/python/python', '.txt')
/python/python
.txt

FFT分析(ノイズ処理前)

ノイズ処理前後での差を分かりやすくするため、 まずはノイズ処理前のデータについてFFT分析を行います。  

#必要データの抽出
f = df['voltage']
# numpy.ndarrayに変換
f = f.values

# ここからノイズ処理前のデータ分析
# 高速フーリエ変換(周波数信号に変換)
F = np.fft.fft(f)
# FFTの複素数をを絶対値に変換
F_abs = np.abs(F)
# 振幅をもとの信号に揃える
F_abs_amp = F_abs / N * 2  # 交流成分はデータ数で割って2倍
F_abs_amp[0] = F_abs_amp[0] / 2  # 直流成分は2倍不要
ポイント

・DataFrameからndarrayへの変換
まず、csvの元データをpandasのDataFrameとして取得しました。
df = pd.read_csv(hogehoge)
その後、電圧の列を1次元のSeriesとして抜き出します。
f = df['voltage]
1次元のデータになったので、数値計算に適したnumpyのndarrayで取得します。
後でFFT分析をするということもあってndarrayにする。
f = f.values

高速フーリエ変換
np.fft.fft(f)FFT出来ちゃう。めっちゃ便利。後述するけど逆FFTももちろん出来てしまう。numpy恐るべし。

FFT分析(ノイズ処理後)

続いて、ノイズ処理+FFT分析のパターン。

# ノイズフィルタリング用にFFT結果コピー
F2 = np.copy(F)
#バンドパス処理(fc1~fc2の帯域以外を0にする)
F2[(freq < fc1)] = 0
F2[(freq > fc2)] = 0
# FFTの複素数をを絶対値に変換
F2_abs = np.abs(F2)
# 振幅をもとの信号に揃える
F2_abs_amp = F2_abs / N * 2  # 交流成分はデータ数で割って2倍
F2_abs_amp[0] = F2_abs_amp[0] / 2  # 直流成分は2倍不要
# 逆FFTで時間信号に戻す
f2_ifft = np.fft.ifft(F2)
# ナイキスト周波数以降も全部ゼロにしちゃったから、振幅を揃えるため
f2_ifft_real = f2_ifft.real * 2 # 実数部の取得かつ2倍して、振幅を元スケールに戻す 
ポイント

・バンドパス処理(fc1~fc2の帯域以外を0にする)
1次の回転数に応じてバンドパスフィルタ(BPF)を行う周波数域を変えています。
例えば1次成分が90rpsであれば、70Hz (= 90Hz-20Hz) 以下および200Hz (= 90Hz× 2 + 20Hz)以上の電圧を0にしています。
200Hz以上が電圧0になっているので、これによって高次ノイズもキャンセルされています。

グラフ化してアウトプット

最後にmatplotlibを使ったグラフ化処理です。

# グラフ表示
fig = plt.figure(figsize=(10.0, 8.0))
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['font.size'] = 10

# 時間信号(元)
plt.subplot(221)
plt.plot(t, f, label='f(n)')
plt.xlabel("Time", fontsize=12)
plt.ylabel("Signal", fontsize=12)
plt.grid()
leg = plt.legend(loc=1, fontsize=15)
leg.get_frame().set_alpha(1)

#周波数スペクトル(元)
plt.subplot(222)
plt.plot(freq, F_abs_amp, label='|F(k)|')
plt.xlabel('Frequency', fontsize=12)
plt.ylabel('Amplitude', fontsize=12)
plt.grid()
leg = plt.legend(loc=1, fontsize=15)
leg.get_frame().set_alpha(1)

# 時間信号(処理後)
plt.subplot(223)
plt.plot(t, f2_ifft_real, label='f2(n)')
plt.xlabel("Time", fontsize=12)
plt.ylabel("Signal", fontsize=12)
plt.grid()
leg = plt.legend(loc=1, fontsize=15)
leg.get_frame().set_alpha(1)

#周波数スペクトル(処理後)
plt.subplot(224)
plt.plot(freq, F2_abs_amp, label='|F2(k)|')
plt.xlabel('Frequency', fontsize=12)
plt.ylabel('Amplitude', fontsize=12)
plt.grid()
leg = plt.legend(loc=1, fontsize=15)
leg.get_frame().set_alpha(1)
plt.show()

# 画像保存
img_name = os.path.splitext(os.path.basename(filepath))[0]
fig.savefig(dataname + '.png')
ポイント

plt.subplot() 引数はplt.subplot(列、行、位置)になる。 例えば1行2列の1番目(左側)にグラフを書く場合は、 plt.subplot(1, 2, 1)のようになる。

ちなみに出力されたグラフはこんな感じ。
ここでのポイントは以下のとおり。
横軸が周波数(Frequency)のグラフで、高周波数側の値が0になってることがわかるかと。 f:id:Zakku44:20201007184441p:plain

参考文献

NumPyのarange, linspaceの使い方(連番や等差数列を生成) | note.nkmk.me

グラフパラメーター | Python / matplotlib - 軸の色、座標ラベルの色や凡例の文字の色など

僕のpandas.SeriesとDataFrameのイメージは間違っていた - Qiita

PandasとNumPyの違いと使い分け方 - DeepAge

【NumPy】高速フーリエ変換とローパスフィルタでノイズ除去 | 西住工房

11. スペクトル解析と窓関数 (やる夫で学ぶディジタル信号処理)

まとめ

FFT処理がこんなに簡単にできてしまうことに感動しました。 ・グラフ化もよくつまずくので書こうかな。