OmniAICreator/Qiita-1.07M huggingface dataset:
slackの新WYSIWYGエディタとたたかう

# はじめにの前に

[からくりアドベントカレンダー](https://qiita.com/advent-calendar/2019/caraquri)の13日目にこの記事で参加することとなりました。
僕もエンジニアですが、今回も技術的なことは他の方に任せるとして、技術的ではないけれどプログラミングの仕事に関わる大事なことを語ろうと思います。よろしくどうぞ。

# はじめに

エンジニアとして仕事をしていて、切っても切り離せないのがslack。そんなslackのテキスト入力欄が最近変わりましたね。

> デスクトップとモバイルデバイス向けに、メッセージの新しい書式設定方法を追加しました。自動の書式設定を使用すれば、メッセージの送信前に表示内容を確認できます。

https://slack.com/intl/ja-jp/help/articles/115004846068

以前はMarkdown風味な書き方でメッセージを編集できたものですが、
![スクリーンショット 2019-12-13 12.23.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/cb254d67-657c-5d65-f525-2a67b38574ca.png)

いつの間にかメッセージ編集中に書式が反映されるようになりました。WYSIWYG(What You See Is What You Get、見たまんまのものが得られる)ってやつです。ちなみにテキストの内容はカレーの材料です。 :curry: 
![スクリーンショット 2019-12-13 12.22.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/db498093-1391-aa01-20ba-1aec47924d7a.png)

だた、Markdown風味なテキスト編集に慣れてしまった我々エンジニアは、かつてのMarkdownを渇望する訳です。

## WYSIWYGエディタを無かったことにする

まあ普通に以前のMarkdown風エディタに戻せるんですが。

`ワークスペース左上メニュー > 環境設定 > 詳細設定 > 入力オプション > 「マークアップでメッセージを書式設定する」にチェックを入れる`

<img width="479.5" alt="スクリーンショット 2019-12-12 23.32.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/ee6d0293-5738-116e-c8f2-30c28eb3935f.png">


## WYSIWYGエディタとうまいこと付き合う

とはいえ、これを機会にWYSIWYGエディタのままslackを利用する人もいるかと思います。
ただ、[slackのWYSIWYGは仕様がちょっとヤバイ話(英語)](https://quuxplusone.github.io/blog/2019/11/20/slack-rich-text-box/) [^1]もあるため、何も考えずに使うとやっぱりちょっと使いにくいです。なので

### ショートカット書式設定を使ってみましょう

|ショートカット|効果|覚え方|
|:--:|:--:|:--:|
|⌘ b|**太字**|**B**old|
|⌘ i|*斜体*|**I**talic|
|⌘ Shift X|~~打ち消し線~~||
|⌘ Shift C|`インラインコード`|inline **C**ode|
|⌘ Shift 7|1. 箇条書き(番号)||
|⌘ Shift 8|・ 箇条書き(行頭文字が点`・`)||
|⌘ Shift 9|> 引用タグ||
|⌘ ⌥ Shift X|`コードブロック`|**C**ode block|

例えば、入力前におもむろに `⌘ Shift C` を押すと
![スクリーンショット 2019-12-13 13.01.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/963f7a8b-19da-bc18-ba60-655bef0e90c8.png)
キャレットが赤くなり、インラインコードが打てるって寸法です。
![スクリーンショット 2019-12-13 13.02.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/34b9aa9c-1cb7-7e24-e87c-7461bab10cc4.png)

これの何が大事って、 
`文字を打つ -> マウスに持ち替える -> 書式設定ボタンを押す -> キーボードに手を添える -> 文字を打つ`
より
`文字を打つ -> ショートカットで書式設定 -> 文字を打つ`
の方が持ち替える手間がないので圧倒的に早い訳です。

slackに限らず、こういったショートカットを覚えれば作業スピードが簡単に上がるので、パソコンを使う仕事の人にとってかなり大事なスキルだと思っています。


みんなも覚えようぜ、ショートカット。

## おまけ - 他のslackショートカットも覚える

|ショートカット|効果|
|:--:|:--:|
|⌘ Enter|メッセージ送信|
|⌘ k|クイックスイッチャー(検索とかできるやつ)を開く|
|⌘ [ または ⌘ ←|前見たチャンネルに戻る|
|⌘ ] または ⌘ →|(戻りすぎた後に)進む|
|⌥ ↑|1つ上のチャンネルに移動|
|⌥ ↓|1つ下のチャンネルに移動|
|⌘ u|ファイル追加メニュー|
|⌘ .|右サイドバーを閉じる/開く|

### 覚えるのが面倒ならば
`⌘ /`でショートカットが見れるので見ながら作業しましょう、いつか覚えます :muscle: 
<img width="479.5" alt="スクリーンショット 2019-12-13 13.36.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/8595b946-20f7-c6a7-ffda-51596bac5837.png">


[^1]: 例えば、「this is a functino named `foo()`」という文。本当は`foo()`ではなくて `bar.foo()`だった！って場合。カーソルを`foo()`の手前に持ってきてbar.を書き足そうとする訳ですが、「this is a functino named bar.`foo()`」となってしまいます。他にも何となく思った結果にならない仕様があるみたいです。試してみてください。
スクレイピングする際の自分なりのベストプラクティス

これは[Python Advent Calendar 2019](https://qiita.com/advent-calendar/2019/python)の13日目の記事です。

~~Python要素薄めになっちゃったけど、スクレイピングの文脈でPythonよく出てくる気がするから許してください~~

# 概要

- (自分なりの)スクレイピングシステム構築方法のベストプラクティス的なものがちょっとだけ定まった
- 公開していろいろ意見を聞いてみたい
- たぶんウェブデベロッパーがスクレピングやるときに知っておくとちょっとだけ幸せになるかもしれない
- 誰かの役に立てればいいなあ(希望)

# 構築方法の重要な点

重要な点は以下の2点

1. DOMにアクセスしてデータを取得するスクレイピングする処理部分はJavaScriptを使う(とくにウェブデベロッパーの方は)
2. Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う

構成図は以下のような感じです。(OSとか書いてないけど、だいたいのイメージ)
Pythonの部分は任意でSeleniumを叩ければなんでもいいです。(以下のPythonはSeleniumを使うモノとして読み替えてください)

![Group 5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91558/d4bfb070-e9ff-09f1-4ad2-e871d2fb8740.png)

# JavaScriptを使ってスクレイピング処理

スクレピング関係のシステムを作るときにめんどくさいのはライブラリ選定だったりします。

個人的にPythonは好きだったりするので、昔は`PyQuery`とか`BeautifulSoup`を触っていました。

**しかし、結構かなり面倒くさいです。**

どういう風にセレクタを書けばいいのかググって実行して、ミスって、修正して、...みたいな感じなります。

最終的な結論が「Chrome Developer ToolsでJSのセレクタを書いて、欲しい情報を取得するJSを書いて、それをSeleniumから実行させる」です。(これが一番楽だと思います。)

(おそらくjQueryを触ったことがある人なら一番とっつきやすいはず)

以下のようなかんじ。

```python
res = driver.execute_script(driver, """
    return (() => {
        // 以下をChrome Developmer Toolsでいろいろ試して作成する
        let tmp = [];
        $(".sample-area li a").each( (idx, element) => tmp.push($(element).attr("href")) )
        return tmp
    })()
""")
```

Chrome Developmer Toolsは以下のようなやつ。F12とか押したらでます。(Consoleタブを開くとJSがページ内で実行できます)
簡単な話、クラス情報とか丸コピして`$(".class").text()`とかやるだけでデータとってこれたりします。

<img width="1027" alt="Screen Shot 2019-12-13 at 18.39.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91558/9b3efc04-27ad-5f47-e742-b20efe8d2202.png">

これをライブラリの特有の関数でいろいろやろうと思うとめんどくさいのはなんとなくわかるとおもいます。

また、スクリプトがJSなのでスクレイピングライブラリに依存せずに、移植も楽です。

# Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う

ChromeDriverの設定とかChromeとのバージョンの相性とかいろいろやるのは人生の無駄遣いです。
Dockerとか環境をまるっともってこれるものを使いましょう。

単純なHTML,CSSのみで構成されているサイトなら`curl`とかで引っ張ってきてやるのもいいと思います。(単純なウェブサイトなら←ここ重要)
サーバーサイドでレンダリングされていても、内部のJSでわりとなんかゴリゴリいじってるサイトは多いです。

Python + Chrome + ChromeDrive + Seleniumの構成のサンプルを作ってみたのでスターをつけてもらうと喜びます。↓
https://github.com/redshoga/python-selenium-container

# 実装の流れ

1. スクレピングで取得したいデータ、場所を明確にする。
2. Chrome Developer Toolsでその取得したいデータが取得できるスクリプトを作成。
3. Seleniumで作ったスクリプトを動かしてデータを煮るなり焼くなりする。

# おわり
ﾏｻｶﾘﾀﾞﾆｹﾞﾛｰ>🐈💨💨　   🔪💨
trigramで自分のツイートからランダム文を生成する

記録です。説明軽くしか書いてないです。

##やり方

####準備

自分のツイート履歴をダウンロードします。Twitterページで「設定とプライバシー」→「アカウント」→「Twitterデータ」→「Twitterデータをダウンロード」でリクエストして、しばらくしてメールアドレスにダウンロードリンクが届くのでそこからダウンロードします。
2019年の夏以降ダウンロードデータの仕様が変わって`tweets.csv`が`tweets.js`になってしまったそうなので、面倒なので他の方が書いてくれたツールを利用して`tweets.csv`を作ります。（https://17number.github.io/tweet-js-loader/ ）

作業場と同じディレクトリにtextフォルダを作ってその中に`tweets.csv`を放り込んで準備完了。


次に`tweet.py`の中身について。
まずは以下の部分で`tweets.txt`を作ります。`tweets.csv`からツイート本文を抜き出してtxtファイルにしています。

```tweet.py
import csv
import re

rawfile = "text/tweets.csv"
infile = "text/tweets.txt"
outfile = "text/tweets_wakati.txt"


with open(rawfile,'r') as f:
    reader = csv.reader(f)
    
    with open(infile,'w') as f:
        for d in reader:
            if len(d) > 2:
                f.write(d[2])
            f.write('\n')
```


次にjanomeを利用して単語を分ち書きにして、モデルに学習させます。janomeが入ってない人は`pip install janome`を先にしてください。
ちなみに、アルファベットや特定の記号、質問箱などを排除してなるべく日本語で意味が通る文ができるようにしています。

```tweet.py
from janome.tokenizer import Tokenizer
t = Tokenizer()


with open(infile,'r') as f:
    data = f.readlines()

p = re.compile('[a-z]+')
p2 = re.compile('[:/.@#質問●]+')

with open(outfile,'w') as f:
    for i in range(len(data)):
        line = data[i]
        if p2.search(line):
            pass
        else:
            for token in t.tokenize(line):
                if p.search(str(token.surface)):
                    pass
                else:
                    f.write(str(token.surface))
                    f.write(' ')
            f.write('\n')
        


words = []
for l in open(outfile, 'r', encoding='utf-8').readlines():
    if len(l) > 1:
        words.append(('<BOP> <BOP> ' + l + ' <EOP>').split())


from nltk.lm import Vocabulary
from nltk.lm.models import MLE
from nltk.util import ngrams

vocab = Vocabulary([item for sublist in words for item in sublist])

print('Vocabulary size: ' + str(len(vocab)))

text_trigrams = [ngrams(word, 3) for word in words]

n = 3
lm = MLE(order = n, vocabulary = vocab)
lm.fit(text_trigrams)


```

最後にランダム文生成です。

```python:tweets.py
for j in range(10):
    # context = ['<BOP>']
    context = ['<BOP>','<BOP>']
    sentence = ''
    for i in range(0, 100):
        # contextのうち最後の2単語から次に繋がる確率0じゃない単語をランダムに選ぶ
        w = lm.generate(text_seed=context)

        if '<EOP>' == w or '\n' == w:
            break

        context.append(w)
        sentence += w

        
    
    print(sentence+'\n')

```
ランダムに10個の文が出力されます。
コピペするときは上に書いたコードを一つのファイルにまとめるか、jupyter notebookでセルに分けて実行するかなどしてください。
モデルの学習に少し時間がかかる場合があるので、後者の方法がオススメです。

##結果

<img width="433" alt=" 2019-12-13 19.00.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244328/cff5ca8d-e652-152e-fb20-692968d0c5d5.png">

上みたいな感じで10個の文が出力されるはずです。
結構面白いので無限に試せちゃいます。皆さんも是非やってみてください。
Ruby on Rails学習メモ

# はじめに
- macOS Mojave 10.14.6
- Ruby 2.6.5p114
- rake 13.0.1
- Bundler version 2.0.2
- rbenv 1.1.2
- rails 6.0.1
- sqlite3 1.4

たくさん学べたので忘れないうちに書きます。

# 環境構築について
環境構築は<a href="https://qiita.com/NAKKA-K/items/e81f3b6d696333701ae1">こちら</a>を参考にしてください。

# 作りたいもの
タイトルにもある通り、ToDoアプリを作りたい。
なぜToDoアプリなのかというと・・・
``CRUD``を学ぶのに最適だと思ったからです（小並）

#CRUDってなんやねん！
そこから入るのかよって思われるかもしれませんが、そこから入らないとわからなかった人間です。
どうも。
CRUDというのはとある英語の頭文字を取っています
``C = Create(作成)``
``R = Read  (読み取り)``
``U = Update(更新)``
``D = Delete(削除)``
となっております。
これを学ぶのが前述通り、ToDoアプリとされています。

# テーブルの準備
事前に``bundle exec rake db:create``でデータベースの作成をしておいてください。
では、早速入っていきましょう
まずはモデルの準備です。

```console
# modelの生成
$ bundle exec rails g model task
```
で生成されます。

# migrationとは？
``migration``とは英語で「移住、移動」といった意味があるみたいです。
Ruby on Railsでいう``migration``にはこういう機能があります
``SQLを書くことなくRubyでデータベース内にテーブルを作成することができる機能``
大雑把にいうとこんな感じで理解しています。

# modelとは？
MVCという言葉を聞いたことがあるでしょうか？
UIが持つアーキテクチャの1つです。
``M = Model``
``V = View``
``C = Controller``
ですね。
これも大雑把に説明していきます
### Model
アプリケーションが扱う領域のデータと引き継ぎ（例：ショッピングの合計額や送料の計算など）

### View
Controllerから情報をもらってそれをユーザーに見やすい形で出力します。
また、ユーザーが入力したものをControllerへ渡す機能もあります。

### Controller
Modelから情報を取得して、それをViewに渡します。
また、Viewから入力されてきたデータをModelへ渡す機能もあります。

大雑把だけどこんな感じだと思います。

# migrationファイルの修正

```sql:db/migrate/yyyymmddhhiiss_create_tasks.db
class CreateTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :tasks do |t|
      t.text :state
      t.text :task
      t.date :limit_date

      t.timestamps
    end
  end
end
```
こんな感じで修正しています。

```ruby
def change
```
defで、``change``というメソッドを定義します。

```ruby
create_table :tasks do |t|
```
は``tasks``というテーブルを作ってます。

```ruby
t.text :state
t.text :task
t.date :limit_date
```
Railsには設定できるカラムがいくつかあります
``t.string``は文字列(255文字)、それ以上が``t.text``になっています。
``t.date``は日付です。
それらを``state``や``limit_date``という変数に宣言してる感じです。

```ruby
t.timestamps
```
これもカラムで``created_at``や``updated_at``などが入っています。
これは自動的に入るのかな・・・？

#テストデータの作成
テストデータがないと、ちゃんと動かないからね。しっかりと作ろうね。

```ruby:db/seeds.rb
@task              = Task.new
@task.task         = 'task1'
@task.state        = 'todo'
@task.limit_date   = '2018-10-10'
@task.save
```

はい。

```ruby

@task = Task.new
```
これはクラス名``Task``に``new``メソッドの呼び出しをしています。
それを``@task``に入れて以下の変数につけてる感じです。

```ruby
@task.task         = 'task1'
@task.state        = 'todo'
@task.limit_date   = '2018-10-10'
```
これはMigrationファイルで宣言した``task`` ``state`` ``limit_date``にデータを格納していってる感じです。
最後に

```ruby
@task.save
```
でデータベースの保存？格納してる感じです。

#migrationの実行/seeding
```console
$ bundle exec rake db:migrate
$ bundle exec rake db:seed
#もし間違えた場合は・・・
$ bundle exec rake rollback
```

#seedingって何ですか？
``seeding``は英語でいうと「種まき」という意味らしい。そのままだね。
これは、``seeds.rb``で書いたコードが``$ bundle exec rake db:seed``で実行される形になります。

#まとめ
とりあえず、長くなりそうなのでここまでとします。
次回はコントローラーの生成、RESTfulの説明ができたらなあと思います。
ではまた！
「~フォルダ内のファイルにアクセスしようとしています」のダイアログが表示されない場合の解決方法

# 概要

- macOS10.15でファイルやOSの機能にアクセスする際のセキュリティが厳しくなりました。
    - [macOS 10\.15 Catalinaでは「User Consent」がさらに強化され、アプリがスクリーンキャプチャの撮影や書類、ダウンロードフォルダへのアクセスする際にユーザーの承認が必要に。 \| AAPL Ch\.](https://applech2.com/archives/20190605-macos-10-15-catalina-more-user-consent.html)
- 例えば、デスクトップ上のフォルダにアクセスしようとすると、下記のような許可を求めるダイアログが表示されます。
- しかしプログラムを書いている中で、許可を求めるダイアログがでないケースが2つあったので、以下で紹介します。

（↓こやつ）
![-w420](https://i.imgur.com/XQ1VuXX.jpg)

# 解決方法1. セキュリティとプライバシーの設定に情報が登録済みでないかを確認
- 一度「~フォルダ内のファイルにアクセスしようとしています」のダイアログで回答すると、下記の場所に情報が記録されます。
- ONにすればアクセスを許可できますが、「~フォルダ内のファイルにアクセスしようとしています」の表示は以後でなくなってしまいます。
- 再度出したい場合は情報をターミナルからリセットする必要があります。例えば以下の通りです。
- `tccutil reset <サービス名> <バンドルID>`
- 詳しくは、[セキュリティとプライバシーの設定値をリセットする（macOS 10\.15 Catalina）](https://qiita.com/IKEH/items/323db8b5ea1a1a28a519)を参照ください。

```sh
tccutil reset SystemPolicyDesktopFolder jp.co.hikeuchi.AccessConfirmSample
```

![-w668](https://i.imgur.com/Xv4qKRF.jpg)

# 解決方法2. スクリーンセーバーレベルのウィンドウが悪さをしている場合
- WindowLevelが`kCGScreenSaverWindowLevel`のウィンドウがあるとき、アクセス許可画面は表示されないので、WindowLevelを変更してやる必要があります。

## 参考
- [NSWindow\.Level](https://developer.apple.com/documentation/appkit/nswindow/level)
- [セキュリティとプライバシーの設定値をリセットする（macOS 10\.15 Catalina）](https://qiita.com/IKEH/items/323db8b5ea1a1a28a519)

## GitHub
- https://github.com/pommdau/Qiita_AccessConfirmSample

## Code

- 簡単に下記を実装
    - 選択したウィンドウのレベルに変える
    - デスクトップファイルの読み込み

```swift
@IBAction func windowLevelChanged(_ sender: NSPopUpButton) {
    switch sender.selectedTag() {
    case 0:
        window.level = NSWindow.Level(rawValue: NSWindow.Level.RawValue(kCGNormalWindowLevel))
    case 1:
        window.level = NSWindow.Level(rawValue: NSWindow.Level.RawValue(kCGModalPanelWindowLevel))
    case 2:
        window.level = NSWindow.Level(rawValue: NSWindow.Level.RawValue(kCGScreenSaverWindowLevel))
    default:
        window.level = NSWindow.Level(rawValue: NSWindow.Level.RawValue(kCGNormalWindowLevel))
    }
}

@IBAction func openButtonClicked(_ sender: Any) {
    let fileData: Data?
    do {
        let fileUrl = URL(fileURLWithPath: "/Users/ikeh/Desktop/sample.txt")
        fileData = try Data(contentsOf:fileUrl)
    } catch {
        // ファイルデータの取得でエラーの場合
        fileData = nil
    }
    print("\(String(describing: fileData))")
}
```

## 動作確認

- WindowLevelが`kCGScreenSaverWindowLevel`のウィンドウがあるとき、アクセス許可画面は表示されません
- スクリーンセーバーのレベルのウィンドウが、アクセス許可画面の表示を妨げているような動作が見られます。

![スクリーンショット 2019-12-13 15.32.59](https://i.imgur.com/uTSeHCv.png)

- その他のウィンドウレベルでは問題なく表示されることが確認できる

![スクリーンショット 2019-12-13 15.32.42](https://i.imgur.com/dr6Gy4L.png)

![スクリーンショット 2019-12-13 15.32.50](https://i.imgur.com/ejSzzKI.png)
Sinatra

#事の発端
Sinatraというワードがググっている最中に出てきてなんぞや？と思い調べて見る。

##Sinatraとは
Sinatra(Padorino)のテンプレート。
Sinatoraいじったことないけれどw
Sinatraは「ruby,ruby on rails勉強しても、いまいち覚えられているかわからない・・・.」
といったときにSinatora。

##特徴
超簡単にかけるらしい。
もちろんSinatraインストール必須。
インストールだけ完了しました。
今後使うかもしれないし。

※まだSinatraについては勉強中なので、この記事は完成ではありません。
完成できない理由としては、rubygemなど他の優先順位が高い勉強をしなければならないからです。

###参考にしたサイト
https://type.jp/s/itac/article5.html
processをkillした日(flaskでAddress already in useというerrorが出た)

起動しようとするとerrorが出た。
指定のprocessが既に使われているようだ。

```perl
OSError: [Errno 98] Address already in use
```
<br>
process を探して

```perl
ps -fA | grep python
```
任意のものを葬る

```perl
kill xxxx(任意)
```

##参考
http://yusuke-ujitoko.hatenablog.com/entry/2018/07/14/144227
https://qiita.com/shuntaro_tamura/items/4016868bda604baeac3c
Rails更新は最低限Controllerのテストが欲しいというお話

じゅんてつです。

2019年はRails4.2のサポートが切れましたが、みなさんのプロダクトは更新されましたか？Rails更新の際に「[Rails アップグレードガイド - Rails ガイド](https://railsguides.jp/upgrading_ruby_on_rails.html)」を参照しているひとが多いと思いますが、まず一番最初に「テスティングのカバレッジ」の話が出てきますよね。

「テスティングのカバレッジってどこまでやればいいの？」

って思ったことありませんか？私はあります。

クラウドワークスのプロダクトのRails5.2に更新した経験から「Controllerのテスト、特に正常系が1パターンあれば最低限どうにかなった」という体感値を得ましたので、簡単に紹介したいと思います。

## クラウドワークスのRails更新で難しかったこと（課題）

- Rubyだけで30万行超の規模で、全体を把握している人間がいなかった
- モノリシックな作りでRailsの変更がどこに影響がでるか把握できなかった
    - 削除または動作が変わったメソッドが与える影響など
- テストが存在しない機能が多々あった

最低限の検知する方法として、自動テストがないと安全には進めないプロダクトだと感じました。逆に、ある程度は安心できるような仕組みもありました。

- Fat Modelに関するSpecが充実していたので安心
- 継続的インテグレーション(CI)の環境が整っていたので安心

先人の汗と涙の上にできあがっている仕組みだと思うと涙が・・・。

## 何をしたか？

Rails5で動かした際、変更点が起因してModelが壊れたら呼び出してるControllerのテストも落ちるよね？という発想で、Controllerのテストを充実させようしました。内容はRails4の状態でController Spec と Request Specを実装していくというもの。レスポンス `200` やリダイレクトを確認するブラックボックステストしか用意できないものもありましたが、コスト以上の恩恵[^1]を得られました。


## どういう恩恵を得たのか？

結果として、機能の死活監視ができるようになり変更が影響する機能がわかるようになりました。もちろん組み合わせをカバーできたわけではないので動作確認を挟む必要はありましたが、アクセスしただけで落ちるようなエラーは事前に検知できて潰し込めるようになりました。

クラウドワークスはおよそ2000のURLが定義されているため、とりあえず生きてるのか死んでるのかが知れるだけでも非常に助かりました。

## 検知できない影響は？

動作は変わったけど正常にレスポンス返しちゃうような変更を検知できません。例えば、`ActiveRecord` が発行するクエリが変わる変更があったら検知できません。今回はそういった類はなさそうだったのでセーフ。

実際のところ気づいていないだけかもしれないけど、それ検知できるようなら何も心配ないレベルまでテストそろった状態だよね。

## Feature Specじゃダメだったの？

多くは語りませんが、Feature Specは採用されませんでした。

- 網羅的に実装するにはパターンが多い(プロダクト規模に起因)
    - クリティカルな機能に絞って実装しようとしても同様であった
    - どこまで実装すれば安心できるのか、誰も言語化できなかった
    - Rails更新以外の変更に敏感で、運用コストが・・・

## まとめ

- 最低限Controllerのテストがほしい
    - テストは無いよりあったほうが良い、あればあっただけ死活監視ができて安心できる
    - Request Specなどのブラックボックステストは最低限欲しい
    - Controller Specがあればなお良い

あくまで「どうにかなった最低限」なので安心安全を保証するものではありませんが、Rails更新の一つの基準になればと思います。


## あわせて読みたい記事

[Rails4へ移行しましたのご報告とブログ連載のお知らせ - クラウドワークス エンジニアブログ](https://engineer.crowdworks.jp/entry/2016/07/07/105733)


# 注釈
[^1]: 時間(金銭)的なコストを掛けて、リスク、確認の実工数、精神的コストの低減のリターン
WebGL超ことはじめ(1)

#WebGL
ついに勉強したくなってしまったので１からやってみる！
BlenderインストールからThree.jsで表示させるまでを行う.

#Blenderのインストールから
ひぇ、英語だぁ…
とりあえず日本語にしてみたい
![スクリーンショット 2019-12-13 17.44.13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/39c29862-38b1-0b74-daeb-6d06bd521ee3.jpeg)
![スクリーンショット 2019-12-13 17.44.34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/d3a0fbb2-ef16-0f4b-caeb-e07b413cb3a9.jpeg)
![スクリーンショット 2019-12-13 17.44.45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/e97f61f0-8ead-c064-f930-c58065c5f8d1.jpeg)

#3Dデータを取り込んでjsonで吐き出したい
~~エキスポーター~~(?) （→エクスポーターw）なるものが必要らしい。
だが該当フォルダ内には不穏なREADME.mdがぽつり…
↓
three.js-master/utils/exporters/blender/**README.md**

開いてみるとなんと！

> **注意：** Three.js JSON形式のBlenderエクスポーターは、glTFワークフローのより良いサポートに焦点を合わせるために削除されました。
推奨される代替案については、[3Dモデルのロード]
https://threejs.org/docs/#manual/introduction/Loading-3D-models

ガーン！！！！
でも調べるとglTFのほうが今後主流(?)になりそうだったので
こちらで進めることにします。

#glTFとは「3DにおけるJPEG」
**glTF** (GL Transmission Format) 
3Dといえば拡張子大戦争的なイメージがあった。
こいつの一強になっていくのかしら…。
拡張子はやはりロマンがありますね。生まれ変わったらpngになりたい。

#glTFエクスポーターを導入する
（1）GitHubでzipインストール
　　https://github.com/KhronosGroup/glTF-Blender-Exporter

（2）「glTF-Blender-Exporter-master」を解凍、任意の場所に格納
（3）Blenderを起動して「Edit」 >　「Preferences」 >　「File Paths」で
　「Scripts」部分にさきほどの場所を指定する
<img width="650" alt="スクリーンショット 2019-12-23 11.56.54.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/9d47263e-29c1-f2f4-061f-769b5e000699.jpeg">

変更はデフォルトでオートセーブにチェックが付いているっぽい。
<img width="610" alt="スクリーンショット 2019-12-23 11.59.16.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/0dd05615-7df0-7128-b7f3-f24cdd5ea03f.jpeg">

（4）「Add-ons」タブに移動、検索ボックスに「glTF」を検索して
  「Import-Export: glTF 2.0 format」にチェックが入っていることを確認する

<img width="652" alt="スクリーンショット 2019-12-23 12.00.32.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/e332d7c3-59f8-6a86-9244-bd10b4d76046.jpeg">

(5)「Export」に「glTF」が出現したら完了！

<img width="442" alt="スクリーンショット 2019-12-23 12.03.06.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168611/c84a7373-dbf4-e6d4-c3c2-df2cf1f960d2.jpeg">

ってここまでやって思ったけど最新のBlenderってデフォルトで選択でき…る…だ…と…。
べっ、別に悔しくなんかないんだからね...？

▼参考記事
https://jp.ubunifu.co/blender/gltf-gl-transmission-format%E3%82%92-blender%E3%81%A7%E4%BD%BF%E3%81%86


#PBRマテリアル に触れる
PBRマテリアルとは、
**物理ベースレンダリング**
Physically-Based Rendering
現実世界を模倣した、光学的に正確なレンダリングのこと。

#Three.jsでglTFを読みこみ
▼参考記事
https://codelabo.com/1192/
Vue.jsで引数をうまく渡せず詰まった時の話〜引数分割束縛〜

## やりたかったことと詰まった背景
- Vue.jsでフロントエンドの実装。一覧表示の画面から１つのアイテムをクリックすると、その詳細ページに飛べるように実装したい。

- 詳細画面のアイテムの情報自体はサーパーサイドからJSON形式で送られてくる。その送られてきた情報が載っているURL＋クリックしたアイテムのidを詳細画面のURLにしたい。 

- 上記の理由より、URLとクリックされたアイテムのidとを引数として関数に持たせようとしたが、ただカッコの中に引数を渡すだけではうまくいかなかった。

- 調べて出てきた下記の、引数分割束縛という書き方で実装しようとした。




## 関数（引数が2つ以上の時の書き方）
```js
//波カッコに入れる。
event({第一引数, 第二引数, 第三引数, 第四引数})
```
引数分割束縛。
引数を波括弧で囲って使用する。




以下今週一番躓いたところ。

```js

// view/detail.vue

    methods: {
      ...mapActions([
        'get_detail_ajax'
      ])
    },
    mounted() {
      this.get_detail_ajax(url, this.id).then(()=>{
          console.log(url,this.id)   //←ここではちゃんと値が取れてる。
          this.detail = this.$store.detail;
        });
    }
```

```js

// store/index.js

  actions: {
    get_detail_ajax(context,{ url, id }) {
      console.log(url, id); //両方ともundefinedに。
      return axios.get(URL_BASE + url + id, {
          headers: {
            "Content-Type": "application/json",
            "Authorization": 'Bearer ' + token
          },
          responseType: 'json',
        })
        .then((res) => {
          Vue.set(this, 'detail', res.data);
        })
    },
  }
```

contextを抜くかもしくは引数に波括弧を使わなければちゃんと値を取得できたが、contextがないといらない情報もたくさん渡ってきてしまうし、個別ページなのでidもURLに含めたい。
（var url="http://〇〇..."と変数を宣言するときに、:idをどうにか含めようとしましたがエラーに。式展開してもダメでした...）

#### 苦肉の解決策
無理やりくっつけて引数を減らしました。

```js
// view/detail.vue

    methods: {
      ...mapActions([
        'get_detail_ajax'
      ])
    },
    mounted() {
      var id = this.id;
      url = url + id //再代入してURLにidを含ませる。
      this.get_detail_ajax(url).then(()=>{
          this.detail = this.$store.detail;
        });
    }
```

```js
// store/index.js

    get_detail_ajax(context, url) {
      return axios.get(URL_BASE + url, {
          headers: {
            "Content-Type": "application/json",
            "Authorization": 'Bearer ' + token
          },
          responseType: 'json',
        })
        .then((res) => {
          Vue.set(this, 'detail', res.data);
        })
    },

```


なぜ波括弧を使うと取得できないのか、また判明すれば追記いたします。

## 追記

親切な方がコメント欄で丁寧に教えてくださったのでメモ。

引数を引数分割束縛で持たせる場合、同じオブジェクト内のものしか持たせることができない。

```js

// view側

var hoge = "第一引数"
var huga = "第二引数"
var moge = "第三引数"

hogeHugaMoge (hoge,huga,moge)

```

とある場合、同じオブジェクトではないので

```js

// store側

hogeHugaMoge ({hoge,huga,moge}){
  console.log(hoge,huga,moge);  //エラーとなる
}
```
上記のようには使用できない。

なので下記のように１つのオブジェクトにまとめて入れて...

```js
// view側

var hogehugamoge = {
  hoge: "第一引数",
  huga: "第二引数",
  moge: "第三引数"
}

hogeHugaMoge(hogehugamoge)
```

```js
// store側

hogeHugaMoge ({hoge,huga,moge}){
  console.log(hoge,huga,moge);  //第一引数、第二引数、第三引数がちゃんと渡ってくる！
}
```
成功！

Vue.jsのactionでは指定できる引数は1つ迄のようです。

上記の方法でちゃんと実装できました！コメントいただき本当にありがとうございました！
【Ruby】採用試験でフィボナッチ数列出たけど解けなかったので復習

##執筆者について
・未経験からエンジニアに転職するため就職活動中
・問題が解けなくて悔しい:japanese_goblin:

##フィボナッチ数列について

>n 番目のフィボナッチ数を Fn で表すと、Fn は再帰的に
>
F0 = 0,
F1 = 1,
Fn + 2 = Fn + Fn + 1 (n ≧ 0)
で定義される。これは、2つの初期条件を持つ漸化式である。
>
この数列 (Fn)はフィボナッチ数列（フィボナッチすうれつ、（英: Fibonacci sequence）と呼ばれ、
>
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, と続く。最初の二項は 0, 1 であり、以後どの項もその直前の2つの項の和となっている。
>
出典：<a href=https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0>wikipedia - フィボナッチ数列</a>


上記に書いてある通り、前の項とその前の項を足してできた和が答えになる数列

## 問題（例）

1, 1, 2, 3, 5, 8, 13, 21 , 34, 55, 89, 144, 233, 377, 610,
のように第n+2項が、 第n+1項と、第n項の和になるよう数列をプログラミングで書く


##解答の考え方(解説してもらった)

まずは例外である1項と2項をif文で1にする。

```rb
if n == 1 || n == 2
  1
end
```

3項目以降は

x=項数とおいて
a = x-2の答え
b = x-1の答え
と置いて
多重代入とtimesメソッドを用いて計算をさせまくる


```rb
a, b = 0, 1
n.times { a, b = b, a + b }
a
```

fiv(4)の場合
（4.times { a, b = b, a + b }の場合）

1回目の計算
{a , b = 1, 0 + 1 }
a = 1になる
b = 2になる

2回目の計算
{a , b = 2, 1 + 2}
a = 2
b = 3

3回目の計算
{a , b = 3, 2 + 3}
a = 3
b = 5

4回目の計算
{a , b = 5, 3 + 5}
a = 5
b = 8

aの値が答えになるので戻り値にaを記載する


a = 0
b = 1にする理由は0項目の答えと1項目の答えだから


```rb
def fiv(n)
    if n == 1 || n == 2
        1
    else
        a, b = 0, 1
        n.times { a, b = b, a + b }
        a
    end
end
````
※PHPで解説もらったのをrubyに変換



##記事書きながら思った解答
```rb
def fiv(x)
  if x == 1 || x == 2
     1
  else
     fiv(x-2) + fiv(x-1)
  end
end

```

多重代入とか知らなかったから解けないんじゃね？知らないかったしと思ったけれども
最初に1項目と2項目をifで答え出して良いならこれでもよかったんじゃないかなと思った。

これならテスト中でもかけた気がした。
rbenvでインストールしたrubyがcommand not foundになる

##現象
rbenvでrubyをインストール後に`$ ruby -v`をしたところ
`$ -bash: ruby: command not found`となってしまった。
こちらの[記事](https://qiita.com/Kaisyou/items/34ea9b5800434c1e09e6)と同じ現象かと思い、
`$ rbenv global 2.6.5`としたが、解決しなかった。
##原因
単純にパスが通っていなかったことが原因なのだが、どこにrubyがインストールされているのかわからなかったため、インストール時と同様に
`$ rbenv install -v 2.6.5`としてみると
以下が表示された。
<pre>
rbenv: usr/local/rbenv/versions/2.6.5 already exists
continue with installation? (y/N)
</pre>
現在インストールされているパスがわかったので、`N`を入力してインストールを中止。
パスを通すために.bash_profileをいじる。

##解決法
`$ vi ~/.bash_profile`で開いて修正を行う。
<pre>
PATH=$PATH:$HOME/bin
</pre>を以下に修正して保存。
<pre>
PATH=$PATH:$HOME/bin:usr/local/rbenv/versions/2.6.5/bin
</pre>以下で再読込して
<pre>
$ source ~/.bash_profile
</pre>もう一度`$ ruby -v`をしてみると…
<pre>
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
</pre>できました。
pukiwiki 文法を markdown 文法に変換する pukiwiki2markdown というツールを公開しました

とはいえ、1年前くらいに書いたツールなんですが、

「そういえば Qiita にどうやって実装したのか、とか書いてないなー」

と思ったので供養がてら記事にしました。(∩´∀｀)∩

# デモサイト
<a href="https://pukiwiki2markdown.saino.me/" _target="blank"><img width="1245" alt="スクリーンショット 2019-12-13 18.12.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/75249e1e-4501-18b7-1189-bf7c4f1cc4dc.png"></a>

https://sandbox.saino.me/pukiwiki2markdown/

画面向かって左側の input に pukiwiki の文章を入れると右側に変換結果が表示されます。

変換結果をクリップボードにコピーすることもできます。

# ソースコード (Github)

https://github.com/kaishuu0123/pukiwiki2markdown

# API っぽく動くエンドポイント

下記コマンドを実行することで、pukiwiki 文法から markdown 形式に変換できます。

body の中に文章を入れる感じですね。

```
curl -XPOST https://pukiwiki2markdown.saino.me/api/v1/convert \
     -H 'Content-Type: application/json' \
     -d '{"body": "*Header1\n**Header2\n"}'
```

# 実装するのに使った技術
## バックエンド
* PHP + Slim Framework 3
    * http://www.slimframework.com/
* pukiwiki のコード本体

Slim Framework というのは本家のサイトでも謳っている通り、PHP のマイクロフレームワークです。
選定理由としては **「pukiwiki のコードをそのまま再利用したかったから」** ということに尽きます。

pukiwiki 文法のパーサーを書こうかな〜とも思ったのですが、pukiwiki の元コードを見るとゴリゴリにロジックが書かれていたので、「これは元のコードを再利用した方が圧倒的に早い」と思い、変換するロジックも PHP 側に寄せました。

(変換ツールなので割と使い捨てっちゃ使い捨てですし)

## フロントエンド
* React
* webpack

こちらはいわゆる「普通のフロントエンド」という感じで、何ら変わったことはしてないです。

ただ単にテキストを入力して変換結果をもらうだけなので、そんなに React の恩恵は受けていません。

# 最後に

自前で建てたい方はリポジトリの中に [docker-compose.yml](https://github.com/kaishuu0123/pukiwiki2markdown/blob/master/docker-compose.yml) を同梱しているのでお試しください。
Docker image 提供は今のところ考えてはいません。本当はやった方がいいのかもしれないけど、変換するだけのツールですので。

今後何らかの改善点があるかな〜と考えましたが、現在のところ割と悪くない精度だったので、このまましばらく運用するかもしれません。

何かご意見あれば Issue や Pull Request は受け付けていますので、よろしくおねがいします٩(๑＞◡＜๑)۶
フロントVue.js、サーバーサイドLaravelの実装方法

# 概要
１つのアプリを、フロントはVue.js、サーバーサイドはLaravelで作成し、Vue.js側でサーバーサイドをAPIとして引っ張ってきて使用したい。

## axios


```store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

const URL_BASE = '全ページ共通のホスト部分URL';
var token = "生成されたトークン";

//新しいVuexを生成
export default new Vuex.Store({
  state: {
    list: []
  },
  actions: {
    //このcontextがないと、urlが上手く渡って来ないことがある。
    get_ajax(context, url) {
      //変数urlはviewで定義する
      return axios.get(URL_BASE + url, {    
         //このheadersは、認証が必要なページの時に記載する。
          headers: {
            "Content-Type": "application/json",
            "Authorization": 'Bearer ' + token
          },
          responseType: 'json',
        })
        //resに送られてきたデータが入っている。
        .then((res) => {
          //下記の記載で、ここの配列listにres.dataをsetしている。
          Vue.set(this, 'list', res.data);
        });
    },
  }
});
```


```views/index.js
//ここら辺は今回関係ないとこ
<template>
  <div>
    <Template/>
  </div>
</template>


<script>
  //これは今回関係なし。
  import Template from "@/common/Template.vue"
  //vuexのmapActionsが使えるようになる。
  import { mapActions } from 'vuex'

  var url = 'このサーバーサイドで実装した、このviewページのurl';

  export default {
    data() {
      return {
        list: []
      }
    },
    //ここら辺も今回関係なし
    components: {
      Template
    },
    //mapActionsにstoreで定義したget_ajaxが使えるように記載する。
    methods: {
      ...mapActions([
        'get_ajax'
      ]),
    },
    //asyncとawaitで非同期通信となる。
    async mounted() {
      await this.get_ajax(url).then(()=>{
        //ここの記載でdataの中の配列listにstoreで定義されたlistを代入している。
        this.list = this.$store.list;
      });
    }
  }
</script>

```




参考資料
https://qiita.com/ksh-fthr/items/2daaaf3a15c4c11956e9
minSdkVersionが17以下のAndroidアプリにUI Automatorを導入する

UI Automatorは、minSDKVersionが17以下だと使えない。
ただ、UIテストの時だけminSdkVersionを18以上をすれば、UI Automatorは使える。
その設定方法を説明する。

# 設定方法
## 1. UI Automatorを導入する

「app/build.gradle」に以下を記述する。

```gradle
dependencies {
    ...
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
    ...
}
```

## 2. テスト用のAndroid Manifestを追加する

「app/src/androidTest/AndroidManifest.xml」に以下を記述する。

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="<your app's package name>">

    <uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="28"
        tools:overrideLibrary="android_libs.ub_uiautomator" />

</manifest>
```

## 3. テストアプリのビルド時にテスト用のAndroid Manifestを参照させる

「app/build.gradle」に以下を記述する。

```gradle
android {
    sourceSets {
        androidTest {
            manifest.srcFile 'src/androidTest/AndroidManifest.xml'
        }
    }
}
```

sourceSetsの記述はやや注意。
テスト用のAndroidManifest.xmlの追加時、Android StudioがsourceSetsの記述を自動で生成する。
自分の場合は、プロダクトコードのAndroid Manifest.xmlとなるような記述になっていた。
その場合は、自動生成された箇所を消して、上のように記述すればOK。

# 備考

この設定をすると、当然、SDK Versionが17以下でUIテスト実行できなくなる。
ただ、UITestをSDK Version17以下でも動くようにするよりも、断然おすすめ。
UI Automatorがあると、Espressoだけ使うときよりテストコードが簡潔になるため。

後、SDKVersion 17(Android 4.2)はさすがに古すぎるので、そもそもサポート対象から外した方が良いかと。
FlutterでListViewの子要素GoogleMapがタップに反応しない場合の対処方法

#結論
```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

ListView(
  children: <Widget>[
    GoogleMap(
      gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
        Factory<OneSequenceGestureRecognizer>(
          () => EagerGestureRecognizer(),
        ),
      ].toSet(),
    ),
  ],
)
```

引用：https://stackoverflow.com/questions/54280541/google-map-in-flutter-not-responding-to-touch-events

#何でこれで動くのか
EagerGestureRecognizerクラスの解説を読む。
>(原文)
>A gesture recognizer that eagerly claims victory in all gesture arenas.
>(意訳)
>この GestureRecognizer は全 gesture のいる闘技場の中でも特に熱心に勝利を主張します。
https://api.flutter.dev/flutter/gestures/EagerGestureRecognizer-class.html

**この文章書いた人、何かキメてたんですかね？？？？**
Eager が熱心という意味らしい。
要するにはユーザータップ時に複数の操作可能オブジェクトがあった場合に、
この Gesture が紐付けられたWidgetが最優先で反応するみたいです。
つまり、デフォルトではListViewのほうが優先度高いんですね……
[Vue.js] v-modelのinputの処理を間引く


# v-modelでの入力する際のinput処理を間引く

input時に動く処理が多くて重くなってしまったので、間引けないか考えた時のメモ
※class styleで記述しているので注意

setterが動く際にlodashのdebounceを使って間引くようにする

```vuejs

<template>
  <textarea v-model="text" />
</template>

<script lang="ts">
import _ from 'lodash'
import { Component, Vue } from 'vue-property-decorator'

@Component
export default class DebounceTextArea extends Vue {
  public _text: string = ""

  public get text(): string {
    return this._text
  }

  // debounceの返り値がFunctionなのでmethodとして定義しておく
  public debounceTextSetter = _.debounce((text: string) => {
    this._text = text
  }, 1000)

  public set text(text: string) {
    // debounceで入力が終わってから値が設定されるようにする
    this.debounceTextSetter(text)
  }
}
</script>
```
UI.vision RPA での Webブラウザテスト自動化の際の 変数のチェック (Selenium-IDE の assert 同等)

# ブラウザテストを自動化したいがツールは何が良いかな
- 発端はSelenium-IDEのプラグインが新しくなったこと

## Selenium-IDE
久々に見たら新しく更新されてました。

- Chromeプラグイン
    - https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd?hl=ja
- Firefoxプラグイン
    - https://addons.mozilla.org/ja/firefox/addon/selenium-ide/

## 使ってみた
StartRecording ボタンを押してブラウザ操作すれば簡単！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108113/c5922b36-f2a2-5762-4b23-cf36a899c64d.png)

- 変数が表示されないのがちょっとな・・・いちいちecho で出すのも微妙
- 待ちとかは割と優秀でちゃんと表示されるの待ってから次へ進む
- cli用の selenium-side-runner はクリックしても押せない場合があるorz

## UI.vision RPA の場合
https://ui.vision/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108113/42858c24-ddad-97d7-7510-dab8ba329afe.png)

- 変数もタブがあって見れます
- UI.vision RPA で assert コマンドが無い orz

## if コマンドで何とか代用可能でした
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108113/77f603b1-9f49-a073-983f-3a39f8936cca.png)

結局どちらが良いかは人それぞれでしょうか…。

- 変数が見れる
- コマンドヘルプがすぐに見れる
- スクリーンショットとか
- CSVとか
- 画像とか
- サンプルが最初からある

現状では上記の点から UI.vision RPA の方が便利かなーと思いますが、
どちらも OSS なんで今後の機能追加に期待します。
<科目> 機械学習　第六章:アルゴリズム2(k-means)

#<科目> 機械学習

目次
[第一章:線形回帰モデル](https://qiita.com/matsukura04583/items/8892d8a41f7ccbcfe739)
[第二章:非線形回帰モデル]
(https://qiita.com/matsukura04583/items/baa3f2269537036abc57)
[第三章:ロジスティク回帰モデル]
(https://qiita.com/matsukura04583/items/0fb73183e4a7a6f06aa5)
[第四章:主成分分析]
(https://qiita.com/matsukura04583/items/b3b5d2d22189afc9c81c)
[第五章:アルゴリズム1(k近傍法(kNN))]
(https://qiita.com/matsukura04583/items/543719b44159322221ed)
[第六章:アルゴリズム2(k-means)]
(https://qiita.com/matsukura04583/items/050c98c7bb1c9e91be71)
[第七章:サポートベクターマシン]
(https://qiita.com/matsukura04583/items/6b718642bcbf97ae2ca8)


##第六章:アルゴリズム2(k-means)

##k-平均法(k-means)とは
+ 教師なし学習
+ クラスタリング手法
    + クラスタリング・・・特徴の似ているもの同士をグループ化
+ 与えられたデータをk個のクラスタに分類する

##k平均法(k-means)のアルゴリズム
+ 各クラスタ中心の初期値を設定する
+ 各データ点に対して、各クラスタ中心との距離を計算し、最も距離が近いクラスタを割り当てる
+ 各クラスタの平均ベクトル（中心）を計算する
+ 収束するまで2, 3の処理を繰り返す
+ 手順
    + 各クラスタ中心の初期値を設定する
    + 各データ点に対して、各クラスタ中心との距離を計算し、最も距離が近いクラスタを割り当てる
    + 各クラスタの平均ベクトル（中心）を計算する
    + クラスタの再割り当てと、中心の更新を繰り返す
+ 中心の初期値を変えるとクラスタリング結果も変わりうる
+ kの値を変えるとクラスタリング結果も変わる

##(演習)k平均法(k-means)を利用して3つのクラスタに分類する
```python
#https://datahexa.com/kmeans-clustering-with-wine-dataset/参考
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import cluster, preprocessing, datasets

from sklearn.cluster import KMeans

wine = datasets.load_wine()
X = wine.data
X.shape
```
```:結果

```

```python
y=wine.target
y.shape
```
```:結果
(178,)
```
```python
wine.target_names
```
```:結果
array(['class_0', 'class_1', 'class_2'], dtype='<U7')
```
```python
model = KMeans(n_clusters=3)
labels = model.fit_predict(X)
df = pd.DataFrame({'labels': labels})
type(df)
```
```:結果
pandas.core.frame.DataFrame
```
```python
def species_label(theta):
    if theta == 0:
        return wine.target_names[0]
    if theta == 1:
        return wine.target_names[1]
    if theta == 2:
        return wine.target_names[2]
df['species'] = [species_label(theta) for theta in wine.target]
pd.crosstab(df['labels'], df['species'])
```
<img width="355" alt="スクリーンショット 2019-12-13 17.46.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357717/a0803c15-a6c6-d4d0-0035-3ec50bf8ebf5.png">

関連サイト
[第一章:線形回帰モデル](https://qiita.com/matsukura04583/items/8892d8a41f7ccbcfe739)
[第二章:非線形回帰モデル]
(https://qiita.com/matsukura04583/items/baa3f2269537036abc57)
[第三章:ロジスティク回帰モデル]
(https://qiita.com/matsukura04583/items/0fb73183e4a7a6f06aa5)
[第四章:主成分分析]
(https://qiita.com/matsukura04583/items/b3b5d2d22189afc9c81c)
[第五章:アルゴリズム1(k近傍法(kNN))]
(https://qiita.com/matsukura04583/items/543719b44159322221ed)
[第六章:アルゴリズム2(k-means)]
(https://qiita.com/matsukura04583/items/050c98c7bb1c9e91be71)
[第七章:サポートベクターマシン]
(https://qiita.com/matsukura04583/items/6b718642bcbf97ae2ca8)
field_error_procで[Rails/OutputSafety]を回避

# field_with_errorsをカスタマイズしたい

### アプリ全体に適用する

```rb:config/application.rb
    config.action_view.field_error_proc = Proc.new do |html_tag, instance|
      %Q(<span class="has-error">#{html_tag}</span>).html_safe
    end
```

参考 : [Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ
](https://yukimasablog.com/rails-field-with-errors)

### ページ単位

app/hoge_helper.rbあたりに書く。

```rb:app/hoge_helper.rb
module SportsHelper
  ActionView::Base.field_error_proc = proc do |html_tag|
    %(<span class="has-error">#{html_tag}</span>).html_safe
  end
end
```

**ただしこれだと、html_safeは使うなとRuboCopに怒られる。**
>RuboCop: Tagging a string as html safe may be a security risk. [Rails/OutputSafety]


#### [Rails/OutputSafety]を回避する書き方

```rb:app/hoge_helper.rb
module SportsHelper
  ActionView::Base.field_error_proc = proc do |html_tag|
    v = ActionView::Base.new
    v.content_tag(:span, html_tag, class: 'has-error')
  end
end
```

参考 : [Railsでformのfield毎にエラーメッセージを表示させる便利設定](https://qiita.com/toyokappa/items/8c0e35ede18f3e2ad321)
1on1での心理的安全性の高め方について

#1．目的
最近マネジメントに興味をもって書籍を読んでいると、
**「心理的安全性」**
という言葉が目に付くようになりました。Google系の本に詳しく書いてあったので読んでみると「これは使える！」と考え、各メンバーのマネジメントとして導入しています。

その中で今回は1on1で個人的に効果があったな、と思った内容をまとめます。

#2．心理的安全性とは
心理的安全性とは「チームが恐怖や不安を感じることなく、安心して発言・行動できる状態になっていること」です。
これが実現できると、各メンバーのモチベーションが上がり、結果として今まで以上の生産性を発揮することが可能となります。

#3．1on1で効果があったこと
- アジェンダを作らない（初回以外）

相手の話を聞くのが主題ですので、こちらからアジェンダを作成しない方がいいです。ただ、初回のみ「何をするの！？」と警戒されていることが多いですので、きちんと目的を伝えるようにアジェンダを作成するべきです。

- 弱さを見せる

自分の弱さを話してください。特に**失敗談を交えて話す**と効果的です。（私はお客様10人ほどに詰められた話や、裁判沙汰になりかけた話をしてます…）

- 傾聴する

できる限り相手の話を聞いてあげてください。ネガティブな話題や愚痴だけでも、否定せずに聞くことが重要です。また、プライベートな話や些細なことでも問題ありません。

- 誠実に正直であること

誠実、正直を心がけてください。特に1on1をする際は自分から声をかけて、遠くにいる場合でも直接会いに行ってください。
夜遅くにやらなければならない場合は、一言「夜遅くに申し訳ないです」と伝えましょう。

- どんな質問にも答える

何か質問事項があれば、答えられる限り答えてください。簡単な質問（検索をかければすぐにわかる）でも答えることが重要です。
（メンター系の本を読むと、答えずに**「考えさせることが重要」**とありますが、日本の教育上、考えさせる力を失っている人がほとんどです。そのため、**考える力のない人に考えさせる方法をとっても効果がないことが多いです**。考えさせるような質問を投げても、途中で会話が止まります…）

- 相手をきちんと見ること

○○さんで効果があったことは△△さんでも効果があるとは限りません。そのため、相手の考えをきちんと聞いて、相手に合った話をしてください。

- 無理にアドバイスしなくていい

わからないことがあれば、わからないときちんと言いましょう。無理に話をする必要はありません。また、一般論でしか答えられない場合は「一般論になるけど～」と前置きして話しをしてください。

- 間違ったことを言ってしまったら謝る

間違いは誰にでもあることです。そのため、1on1で間違ったことを言ってしまったのなら、そのことをきちんと謝りましょう。

- 今後どうなりたいか聞く

技術的なことを鍛えたいのか、チームをマネジメントしたいのか、何かを教えるのが好きなのか、相手の価値観がよくわかる質問です。

- 何かあれば率先して手伝う、教える

相手が何か問題を抱えていれば、解決できるように手を貸しましょう！
（マネジメント系の本では、**「相手に解決することを則すことが重要」**で、手伝ってはいけないと書いていることがあります。ただ、ほとんどの企業では自主性を尊重するという名の**「放置」**をしていることが多く、まともな教育を受けている人が少ないのが現状です。そのため、**「放置」を受けていた人には、きちんと教えた方が効果があります。**）

- 成長するには時間がかかることを意識する

個人にもよりますが、1度教えたことを次から完璧にできる人はほとんどいません。そのため、同じ質問が何回来ても根強く教え続けることが重要です。
（その時、この人ならどう言えばわかりやすく伝わるのだろう？と考えることも大事です。）

#4．心理的安全性が高まると
心理的安全性が高まると、1on1の会話が自分から相手に移っていきます。すなわち、相手が率先して話してくれるようになります。
この時一番いいなと感じたのが、**「事実がわかる」**ことです。これまで、これが原因だと思っていたことが、相手の話によって間違えていたことに気付くのです。間違った原因に対してこれまで対策を打っていたので、うまくいかないのは当たり前と言えるでしょう。

**「心理的安全性が最も重要」**と言われている理由がここにあると思っています。
Python備忘録

###はじめに
[未来電子テクノロジー](https://www.miraidenshi-tech.jp/intern-content/program/)でインターンをしているやっきーです。

まだまだ勉強中のため、間違いがあればどんどん指摘してください。

#Pythonの特徴
##変数宣言をしない
c言語などとは異なり、変数を定義する際に宣言をする必要がありません。変数の条件(数字以外の文字から始まる、特別な単語と被らないようにする、など)を満たしていれば宣言なしに使い始めることができます。

##中かっこ { } を使わない
pythonでは、{}を使いません。for文やif文の中身はインデントで区別します。

```py
for i in range (n): # ： で区切る
    print(i*i)      #for文の中の処理
input()             #for文の外の処理　
```

#主な記法
##出力
標準出力を表示するためには```print()```を使う。

```py
print("hello")
#hello
a = 5
print(a)
#5
```

ただし、数字と文字を混合して表示するときは型変換をしなければならない。

```py
a = 5
print(a + "hands")
#エラー

print(str(a) + "hands") #数値aを文字列に変換
#5hands
```

##入力
キーボードからの標準入力を受け付けるには、```input```を使用します。

```py
a = input()

#helloと入力

print(a)
#hello
```

inputを使用すると文字列が格納されるため、数字として扱いたいときは型変換をする必要があります。

##関数宣言
関数を作るときは、```def```を用います。

```py
def sum(a, b):
   return a+b
```

##文字列の置換
標準入力を```input()```で取得したときに、一部の文字を変えたい場合、```replace()```を用います。
実際の使用例は以下の通りです。

```py
line = input()
# "co worker" と入力
line_2 = line.replace(' ', '-') #スペースを - (ハイフン)に置換
print(line_2)
# "co-worker" と出力
```

##文字列の一部を変更する
例えば、以下のように2文字目を変更しようとしても、エラーとなり、うまく動作しません。

```py
string = "worm"
string[1] = "a"
print(string)
#warmとしたい

#TypeError: 'str' object does not support item assignment
```
このような場合、解決策として2通りの方法があります。

###文字列をリストに変換する
文字列(str)は中身を変更できませんが、リスト(list)は変更ができます。そのため、文字列をリストに変換して、特定の文字を変更した後、再び文字列に変換すればよいです。

```py
string = "worm"

stringList = list(string) #リストに変換

stringList[1] = "a" #2文字目をaにする

srting2 = "".join(stringList) #リストを文字列に変換

print(string2)
#warm
```

##文字列を分けて、間に挿入する
今回の例では、文字列の１文字目までと3文字目以降の文字の間に入れたい文字を挿入します。

```py
string = "worm"

new_str = string[:1] + 'a' + string[2:] #stringの1文字目と3文字目以降を代入、その間にaを挿入

print(new_str) 
#warm
```

##リストから重複を除外
一次元リストの重複を除外するにはset()を使います。
set()を用いることで重複が排除されたset型のオブジェクトとなります。リスト型として扱うためには、list(set())とすればいいです。

```py
nums = [1, 2, 10, 1, 3, 1, 4, 2, 3]

nums2 = list(set(nums))

print(nums2)
#[1,2,10,3,4]
```

#フレームワーク
pythonには、特定の機能をこなすためのプログラムがまとめられたフレームワークが多数存在します。フレームワークを活用することで、より効率的に開発を行うことができます。
pythonのフレームワークとして以下のようなものがあります。

- Django
- bottle
- Flask
- Tornado
- Plone

今後は、Djangoについてさらに学習していきます。


#参考URL
[Solve Python | HackerRank](https://www.hackerrank.com/domains/python)
[Pythonで、文字列の一部の文字を変更する - minus9d's diary](https://minus9d.hatenablog.com/entry/20130528/1369745960)
[Pythonでリスト（配列）から重複した要素を削除・抽出 | note.nkmk.me](https://note.nkmk.me/python-list-unique-duplicate/)
[2019年Pythonのおすすめフレームワーク完全版！各フレームワークを徹底比較！ | エンジニア案件紹介](https://www.high-performer.jp/engineer/trend/python-framework/)
vimの中からtmuxを操作する

この記事はdotfileアドベントカレンダーの15日目です！

# はじめに
みなさんvim + tmux生活エンジョイしていますか？
エンジョイしている方、エンジョイしたい方にオススメの設定をお教えします。


ズバリ、`vimの中からtmuxを操作する`というものです。
基本的にはこちらを参考にし、コードをリファクタしました。
[15年目のVim | POSTD](https://postd.cc/vim3/)
動きとしては[こちらの動画](https://statico.github.io/images/vim/vim3-tmux.mp4)のような形になります。

tmuxにはcommandでpaneに好きなコマンドを送ることができる、
`tmux send-keys`というコマンドがあります。

# 実態
試しにお手元のtmuxで新しいセッションを開き、
3つほどpane分割をした後以下のコマンドを実行してみてください。

```shell
$ tmux send-keys -t $(tmux display-message -p "#S"):0.2 'echo hogefuga' c-j
```

おそらく別のpaneにて`echo hogefuga`というコマンドが実行されたかと思います。
(これで実行されない場合、おそらく`tmuxのwindowやpaneの番号が1から始まる`といった設定をしている
ことが考えられます)

`-t`オプションにて送信先のpaneを指定します。
フォーマットは`session名:window番号.pane番号`となっております。
今回は`$(tmux display-message -p "#S")`にてsession名を取得し、柔軟に仕上げました。

こちらをvim内で`system()`を用いて実行してあげる、というものです。

# 参考実装
apiのエンドポイントを開発しているときとかに別paneにcurlとjqを走らせておいて、  
こいつを用いてvim内からそのcurlを走らせたりすると便利ですね。
こんな感じ

![gamen_test1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270408/8f25a9f7-02f6-9432-bed7-27d78ec4d5bd.gif)

```vim
nnoremap <leader>rt :call TmuxPaneRepeat()<CR>

let g:target_pane = '2'
let g:repeat_command = 'c-c c-p c-j'
function! TmuxPaneRepeat()
  echo system('tmux send-keys -t '.g:target_pane." ".g:repeat_command)
endfunction
```

個人的にいい感じだと思ってるのは、送信するコマンドをグローバル変数にしているので、
送信するコマンドを別のものにしたかったらその場で変数をいじるだけになってるポイントです。
Slimについてざっくりまとめて見た

何を勉強したか、
何を理解したか、
まとめるためにここに書きます🐰

#Slimとは
ruby製テンプレートエンジン。
HTMLの代替できる。

##特徴
めちゃシンプルにかける。
そのため見やすい。
コード量が少ない。
そんなに難しくない。

##Slimのコマンド
Slimで使うコマンドはslimrb
slimrbとなるのは、多分ruby製のテンプレートエンジンだからかな？

##Slimの拡張子
拡張子は.slim

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Slim書いて見た</title>
  </head>
  <body class="sample">
    <div id="contents">
      <h1>Slim</h1>
      <img alt="Slimだよ" src="http://www.slim.com/images/slim.jpg" />
      <p>アダダダダダダダダダ</p>
      <p>アダダダダダダダダダ</p>
    </div>
  </body>
</html>
```

```index.slim
doctype html
html
  head　
    meta charset="utf-8"
    title Slim書いて見た
  body.sample
    #contents
      h1 Slim

      img src="http://www.slim.com/images/slim.jpg" alt="Slimだよ"

      p アダダダダダダダダダ

      p
        | アダダダダダダダダダ
          アダダダダダダダダダ
```

##コメントの書き方
Slimのコメントは/を使うとコメントにすることができる。
コードのはじめに/を書く。


###豆知識
 | (パイプ)って読むんだそうな。知らなかった。

###詳しくはSlimの公式github pageへ
https://github.com/slim-template/slim/blob/master/README.jp.md

###参考にしたサイト,参考になるサイト
https://qiita.com/yterajima/items/53fd0387279510ff082a
https://qiita.com/pugiemonn/items/b64171952d958dc4d6be
オフライン環境でAnsible #2 ~Ansibleトラブル編~

#はじめに
普段は、地道に最下層から仮想環境を作成しクライアントOSを導入するインフラエンジニアです。なので、専門用語はもちろん、自動化や効率化とは若干無縁。そんな人間がAnsible使うぞ！使えるようにするぞ！を書き下してる記事です。（#1同様）


前回の[オフライン環境でAnsible #1](https://qiita.com/mezookey_1012/private/ce83a1e73612bc87ae6e) で、サーバーとクライアントの設定が終わったため、これからAnsibleを流します。


#トラブルの始まり…

まずは、Playbookを流す前に実際にAnsibleが動くことを確認したいのでコマンドライン上で操作。今回はIDと名前を指定して、新しいユーザー・グループを作成してみることに。
それじゃあ設定流してみよう。と、動き出したら、エラーが上がりました。

```
ansible all -m group -a "name=AAA gid=10000"
[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default, this will change,
 but still be user configurable on deprecation. This feature will be removed in version 2.10. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

{IP-ADDRESS} | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
    "unreachable": true
}
# ansible Ansible-Client -m group -a "name=AAA gid=10000"
```
まず一つ。SSHを行う接続先のユーザーが指定されていないので、Ansibleサーバーと同じrootユーザーで接続しようと試みる。rootユーザーとしての認証は通していないので、もちろん、アクセスはできない。

ということで、接続ユーザーを確定させて接続することに。やり方は[AnsibleでのSSH接続ユーザー指定方法](https://qiita.com/NewGyu/items/6a6ba033588514dbca9a)から。

```
# vim /etc/ansible/hosts
...
[Ansible-Client]
ansible-client ansible_ssh_user=ansible
```

これでよし。ansibleユーザーを使ってくれるようになったので、再度チャレンジ

```
# ansible Ansible-Client -m group -a "name=AAA gid=10000"
[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default, this will change,
 but still be user configurable on deprecation. This feature will be removed in version 2.10. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

{IP-ADDRESS} | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "cmd": "/sbin/groupadd -g 10000 AAA",
    "msg": "[Errno 13] Permission denied",
    "rc": 13
}
```
パーミッションの問題。sudo権限はつけたけど、sudoにパスワードが必要だから、ssh認証は突破してもエラーが返ってくる。そりゃそうか。面倒だな。
パスワードなしの認証等を今回気にすると面倒なのでrootに公開鍵を渡してrootで実行することに。

```
# ansible Ansible-Client -m group -a "name=AAA gid=10000"
[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default, this will change,
 but still be user configurable on deprecation. This feature will be removed in version 2.10. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

{IPADDRESS} | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 10000,
    "name": "AAA",
    "state": "present",
    "system": false
}
```

FINALLY!!!やっとできた。わーい。
デバッグモードを用意してinvokeされるxaml内にダミーデータを用意する

みなさんは、UiPathでロボットを用意するときにデバッグモードを使っていますか？

私がロボットを作る際には必ずデバッグモードを内部に組み込みます。たとえば、次の図のようにフロー条件分岐をかませてダミーのデータを読ませてから進むのか、受け取った本番のデータを使って処理を進めるのかを切り替えたりします。

ダミーデータは、**引数で受け取るはずの値の分だけすべて**用意します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/5229162f-1372-ec3e-680b-a569f105222b.png)

デバッグモードというと、通常では呼び出せない関数やプロパティが呼び出せたり、より多くの詳細なログを出力するイメージもありますが、UiPathにおいても同じ発想でログの数を増やしてみてもいいかもしれないですね。

ただ、アプリケーションなどの開発とは異なり、UiPathでは通常時であれデバッグモードであれ出力しておくべき内容にあまり差異がないように思うので、個人的にはダミーデータを読ませるか読ませないか以外について差は作らないようにしています。

## デバッグモード用のフラグ変数

デバッグモードかどうかを切り替える変数は、変数ではなく引数として管理します。そして、初期値は`False`です。

このxamlファイルを直接編集するとき、ダミーデータを読ませるような場合には手動でこちらを`True`にし、作業が完了したときには必ず`False`に戻します（命名規則は[UiPathのワークフローデザイン](https://docs.uipath.com/studio/lang-ja/docs/workflow-design#section-naming-conventions)を参照しています）。

![コメント 2019-12-13 180655.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/59da294e-9746-7db4-c2e6-44085c49968b.png)


初期値が`False`である理由は、単純に他のxamlファイルからinvoke（ワークフローファイルの呼び出し）する際、いちいち`in_IsDebugMode`を`False`にするのは面倒だからです。


## デバッグモード（ダミーデータ読み込み）を用意するメリット

1つのプロジェクトを複数人で開発するとき、invokeするためのxamlファイル単位で分担作業をしているような状況で、本番に使うためのファイルが用意できないときに重宝します。

汎用的なxamlファイルを作っておいて、別のプロジェクトで流用するようなケースでも役立ちます。

いったんすべてのxamlファイルを組み合わせてみて、デバッグモードを`True`にして走らせることで、本番のリソースがない状態でもロジックがうまくいっているのかを検証することができます。

ただし、組織内でxamlファイルを作るときのデバッグ用のフラグ変数を`in_IsDebugMode`で共通化させておくことが重要です。また、invokeするときには必ず呼び出し元で定義されているデバッグモード変数を受け渡すように設計しておく必要があります。そうしておけば大元のxamlのフラグ変数を変えるだけで全体のデバッグモードを切り替えがうまくいきます。

## まとめ

1日だけ空いているのを見て勢いでアドベントカレンダーに登録したものの、普段やっているのはUiPathとJavaScriptでこまごまとしたWeb開発を行うことがほとんどなので、UiPathに特化した内容が書けませんでした。。。

同僚のほうがよっぽど優れたUiPathの活用方法を知っているので、来年はぜひ同僚に書いてもらいたいと思います笑

お粗末様でした…。