// This project is licensed under the MIT license.// // Copyright (c) 2020 Cookpad Inc.// // Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions:// // The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.publicfinalclassMyCustomButton:UIControl {
privatestaticletcornerRadius:CGFloat=4privatelettitleLabel= UILabel()
privateletsubtitleLabel= UILabel()
privatestaticletinsets= NSDirectionalEdgeInsets(
top:5,
leading:5,
bottom:5,
trailing:5
)
privatefuncsetUp() {
// ユーザーの文字サイズの設定によってサイズの変わるフォントを使う// `UIFont.preferredFont(forTextStyle:)`の代わりに`UIFontMetrics.default.scaledFont(for:)`を使っても良いです
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
titleLabel.adjustsFontForContentSizeCategory =true
subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
subtitleLabel.adjustsFontForContentSizeCategory =true
titleLabel.numberOfLines =0// 行数制限なし
titleLabel.textAlignment = .center
subtitleLabel.numberOfLines =0// 行数制限なし
subtitleLabel.textAlignment = .center
letverticalStackView= UIStackView()
verticalStackView.axis = .vertical
verticalStackView.alignment = .center
verticalStackView.isUserInteractionEnabled =false// タッチイベントはこのボタンまで来てほしい
verticalStackView.translatesAutoresizingMaskIntoConstraints =false
addSubview(verticalStackView)
// 左右上下の制約にinsetsの値を活用しても良いのですが、今回はUIStackView.directionalLayoutMarginsを使ってみました
verticalStackView.topAnchor.constraint(equalTo:topAnchor).isActive =true
verticalStackView.bottomAnchor.constraint(equalTo:bottomAnchor).isActive =true
verticalStackView.leadingAnchor.constraint(equalTo:leadingAnchor).isActive =true
verticalStackView.trailingAnchor.constraint(equalTo:trailingAnchor).isActive =true// stack view内に余白を少し入れておきます
verticalStackView.isLayoutMarginsRelativeArrangement =true
verticalStackView.directionalLayoutMargins =Self.insets
verticalStackView.addArrangedSubview(titleLabel)
verticalStackView.addArrangedSubview(subtitleLabel)
// stack viewのおかげで隠れたビューがスペースをとりません
subtitleLabel.isHidden =true
layer.cornerRadius =Self.cornerRadius
clipsToBounds =true
updateColors()
updateAccessibility()
}
privateenumDisplayState {
case disabled
case enabled
case enabledHighlighted
}
privatevardisplayState:DisplayState {
if isEnabled {
if isHighlighted {
return .enabledHighlighted
} else {
return .enabled
}
} else {
return .disabled
}
}
privatefuncupdateColors() {
lettextColor:UIColorletbackgroundColor:UIColorswitch displayState {
case .disabled:
textColor = .white
backgroundColor = .lightGray
case .enabled:
textColor = .white
backgroundColor = .orange
case .enabledHighlighted:
textColor = UIColor.white.darkened
backgroundColor = UIColor.orange.darkened
}
self.backgroundColor = backgroundColor
titleLabel.textColor = textColor
subtitleLabel.textColor = textColor
}
publicoverridevarisHighlighted:Bool {
didSet {
updateColors()
}
}
publicoverridevarisEnabled:Bool {
didSet {
updateColors()
updateAccessibility()
}
}
publicoverrideinit(frame:CGRect) {
super.init(frame:frame)
setUp()
}
publicrequiredinit?(coder:NSCoder) {
super.init(coder:coder)
setUp()
}
publicvartitle:String {
get {
titleLabel.text ??""
}
set {
titleLabel.text = newValue
updateAccessibility()
}
}
publicvarsubtitle:String {
get {
subtitleLabel.text ??""
}
set {
subtitleLabel.text = newValue
subtitleLabel.isHidden = newValue.isEmpty
updateAccessibility()
}
}
privatefuncupdateAccessibility() {
isAccessibilityElement =truevaraccessibilityTraits:UIAccessibilityTraits= .button
if!isEnabled {
accessibilityTraits.insert(.notEnabled)
}
self.accessibilityTraits = accessibilityTraits
accessibilityLabel = [title, subtitle].filter { !$0.isEmpty }.joined(separator:"\n")
}
}
TrivyとAWSの各種マネージメントサービスを利用し、コンテナイメージの脆弱性スキャンパイプラインを構築しました。AWSのサービスと接続することから、基本的な制御の部分にはLambdaを利用し、サーバレスなアーキテクチャになっています。デプロイにはAWS CDK(Cloud Development Kit)を利用しています。
options:- name: sceneName
question: Sandbox scene name?
description: new Sandbox scene name to generate. (e.g. RecipeDetails).
type: string
required:true- name: moduleName
question: Destination target?
description: module name to generate new sandbox scene for. (e.g. RecipeDetails)
type: string
required:truefiles:- template: AppDelegate.swift.stencil
path:"{{ moduleName }}AppDelegate.swift"
$ ./scripts/generate-sandbox
[14:00:14]: Welcome to Sandbox Scene generator
[14:00:14]: What target do you want to make sandbox for1. MyFeature
2. MyAwesomeFeature
? 1[14:00:22]: Enter new Sandbox Scene name to generate. Upper camel case is recommended. (like RecipeDetails)
MyFeatureDetail
[14:00:40]: Generating MyFeature/MyFeatureDetail
[14:00:40]: $ /path/to/ios-cookpad/scripts/mint run Genesis genesis generate /path/to/ios-cookpad/templates/SandboxScene.yml --destination /path/to/ios-cookpad --option-path /var/folders/p7/g0t6l0zx00sbdxxrnm7wq8d80000gp/T/options20200714-98239-1lwms1g.yml
[14:00:40]: ▸ Generated files:
[14:00:40]: ▸ Sandbox/MyFeature/MyFeatureDetailSandboxScene.swift
[14:00:40]: ▸ Sandbox/MyFeature/AppDelegate.swift
$ gem-codesearch "^class Set$"
/srv/gems/ConstraintSolver-0.1/lib/extensions.rb:class Set
/srv/gems/GoNodes-0.0.1/lib/monkeypatch/set.rb:class Set
/srv/gems/Narnach-minitest-0.3.3/lib/set_ext.rb:class Set
/srv/gems/Ron-0.1.2/lib/ron.rb:class Set
/srv/gems/acapela-0.8.1/script/create_voices.rb:class Set
/srv/gems/annlat-0.0.1/lib/annlat/LaRuby.rb:class Set
/srv/gems/antlr4-0.9.2/lib/antlr4/base.rb:class Set
...
というわけで、本記事では Cookpad Pad 2 を例に取りながら、自作キーボードキットを作成する方法について解説します。キーボードの開発はさまざまなノウハウが公開されているため、実際のところそれほど難しくはありません。本記事ではキーボード開発についての完全な手順を説明するというよりも、有益な記事やリソースを紹介することで、全体の流れとして個々のノウハウを結びつけることを目指します。
まず、最初に行なうのは、どのようなキーボードをつくるのか構想することです。Cookpad Pad 2 は 6 キーのマクロパッドです。デフォルトでは「C」「O」「K」「P」「A」「D」の 6 文字が打てるので、「cookpad」と打つときに便利なキーボードです。初代のモデルは、2019年頃に製作をして、ノベルティとして個人的に配布をしていました。
デザインは、この初代 Cookpad Pad を踏襲するものとします。 6 キーを格子状に配置したシンプルなデザインです。また、ケースは PCB を 2 枚のプレートで挟み込んだ、サンドイッチマウント構造にします。この構造は自作キーボードでよく採用される構造で、安価に作成できることがメリットです。
それから、コネクターとして USB Type-C を採用します。そのために、 Pro Micro というマイコンボードを利用しない方針とします。 Pro Micro は自作キーボードに必要な電子部品が実装されているので、基板の設計を単純にすることができます。一方で、Micro USB Type-Bコネクタが採用されていたり、ピンヘッダでマイコンボードを取り付けるため、設計上の制約がでてしまうという欠点があります。そこで、 Pro Micro の利用をするのではなく、直接基板に同等の機能を実装していきます。
I'd like to order PCBA service. Could you give me a quotation for attached files? The shipping address is as follows. I would prefer OCS as a shipping method.
#define VENDOR_ID 0xFEED#define PRODUCT_ID 0x9009#define DEVICE_VER 0x0002#define MANUFACTURER Cookpad Inc.#define PRODUCT Cookpad Pad#define DESCRIPTION A six keys macro pad made by Cookpad.
config.h:31-45では、キーマトリクスの定義を行ないます。Cookapd Pad は 2行 × 3列 のマトリクスですので、そのように定義をします。さらに回路図を確認し、ピンとの対応も定義します。
社内の API サービスは OAuth2 をベースとした自前の認証認可サービスで認証認可を行うことが一般的です。これらの API サービスに対して負荷試験を行う場合、アクセストークンをリクエストヘッダにセットしたり、アクセストークンの有効期限が切れていた場合にリフレッシュする処理が必要になります。Artillery には、リクエストの前後やテストシナリオの前後でフックする仕組みがあり、これを使ってアクセストークンのハンドリングを実装しました。
レシピサービスは社内で最も歴史のあるサービスで、内部のマイクロサービス化やリファクタリングは進んでいるものの、それ専用の仕組みがあったりと複雑な構成です。レシピサービスについて、専用の負荷試験環境を構築することは非常に難しく、また大きな労力がかかることは予想できたため、細心の注意を払いながら「本番環境」で負荷試験を行いました 1。テストシナリオは基本的に開発チーム側で準備してもらいつつ、レビューは SR グループでも行いました。負荷試験はテストシナリオを微修正しつつ何度か実行し、ミドルウェアのボトルネックなどいくつかの脆弱な箇所が洗い出されました。
サービス X の ECS サービスのスケーリングポリシーの最大タスク数に当たってしまい、リソースが不足しレイテンシが増加した。最大タスク数を引き上げた
サービス Y のバックエンド Elasticsearch が CPU リソース不足になりレイテンシが増加した。Elasticsearch の Data ノードのスケールアウト、N+1 クエリの解消、追加でレスポンスのキャッシュを実装が行われた
サービス Z のバックエンド MySQL が CPU リソース不足になりレイテンシが増加した。Z 内でのキャッシュの実装の見直しが行われ、さらに MySQL 接続ユーザやコネクション周りの設定不備も見つかった
最初のうちは、開発チームと SR グループで一緒に負荷の見守りを行っていましたが、終盤はほとんど開発チームだけで負荷試験の実行や中断ができるようになっていました。結果的に想定のテストシナリオをすべてクリアし、キャパシティに自信を持って 100% リリースすることができ、キャパシティ起因の問題は発生しませんでした。本番環境での負荷試験は、それ自体が大きなテーマであるため、このエントリではあえて詳細を書いていません。近いうちに別のエントリとして公開したいと考えています。
社内での Web サービスの負荷試験について、現状と改善の余地を述べ、Serverless-artillery を使った負荷試験の検証、より利用しやすくするための Web コンソールの開発に至るまでを説明しました。開発した Web コンソールは、実際に数回負荷試験に利用されています。テストシナリオのレビューで SR グループが最初関わることもありますが、その後は開発チームがほとんど自分たちで負荷試験のサイクルを回せているという所感です。これ以外にも、負荷試験に利用できる周辺の仕組み2が整ってきており、負荷試験、さらにはキャパシティプランニングが開発において当たり前となっていくような開発体制になることを目指していきたいです。
また、今回コンセプトとして据えた「目を見て話せる」というのはキッチンで料理をするシーンでは微妙で、もう少し洗練させるか、もっと別の良いトリガーを考える必要があると感じました。
これもモバイル向け OS の画像認識ライブラリの性能や仕様によるところがあるため、例えばペーパープロトタイピングなどでは実感するのが難しく、実際に簡単なアプリケーションを作って料理をしたおかげで得られた収穫でした。
このように、技術的な検証を含む日常的なプロトタイピングにおいて、 Flutter を活用することでモバイル向け OS のアプリケーションを簡単に開発し、プロトタイピングすることが出来ました。 今後も、より多くのプラットフォームで動くようになることを期待しつつ、さまざまなプロトタイピングのシーンで活用していこうと思います。
解析結果をスタッフに利用してもらう方法として,データベースを利用せずに予測 API を用意する方法も考えられます.
今回のタスクの目標は「すでに投稿されたレシピからの料理用語の自動抽出」であり,これはバッチ処理であらかじめ計算可能です.
このため, API サーバを用意せずにバッチ処理で予測を行う方針を採用しました.
select
, s.recipe_id
, e.name
, e.category
from
recipe_step_named_entities as e
innerjoin recipe_steps as s on e.step_id = s.id
where
e.category in ('Tg')
and s.recipe_id = xxxx
$ allennlp --help
2020-11-05 01:54:24,567 - INFO - allennlp.common.plugins - Plugin allennlp_optuna available
usage: allennlp [-h][--version] ...
Run AllenNLP
optional arguments:
-h, --help show this help message and exit--version show program's version number and exitCommands: best-params Export best hyperparameters. evaluate Evaluate the specified model + dataset. find-lr Find a learning rate range. predict Use a trained model to make predictions. print-results Print results from allennlp serialization directories to the console. retrain Train a model with hyperparameter found by Optuna. test-install Test AllenNLP installation. train Train a model. tune Optimize hyperparameter of a model.
最適化したいハイパーパラメータを local lr = std.parseJson(std.extVar('lr'))のように変数化しています.
std.extVarの返り値は文字列です.機械学習モデルのハイパーパラメータは整数や浮動小数であることが多いため,キャストが必要になります.
浮動小数へのキャストは std.parseJsonというメソッドを利用します.整数へのキャストは std.parseIntを利用してください.
2017年以前クックパッドアプリにはアーキテクチャと呼べるようなものが存在していませんでした。大まかに API 通信や DB 操作等のデータ取得箇所を分離し、複雑なロジックを持つ場合は Manager, Util 等の強いオブジェクトが生成されていましたが、それ以外は Activity / Fragment に処理を直接記述することがほとんどでした。
Go で言われているらしい "Do not communicate by sharing memory; instead, share memory by communicating."ということですね。Go と異なるのは、Go はいうてもメモリをいじってコミュニケーションできてしまう(メモリを共有しているので)のですが、Ractor ではコピーしちゃうので、そもそも共有ができません。Go は「気をつけようね」というニュアンスですが、Ractor では「絶対にさせないでござる」という感じです。
ちなみに、何かのカウンタなら多少の誤差は許されることもあるかもしれませんが、例えば STM でよく例に出てくる銀行口座の残高の移動というタスクにおいては大問題になってしまうかもしれません。例えば、A さんから B さんに n 円送金するとき、A さんから残高を減らして、B さんに残高を追加する、という処理になります。このとき、Aさんから残高を減らしたタイミングで他の Ractor から A, B 各氏の口座が観測され、世界から n 円消える、という瞬間を目撃していまいます。それはまずい、あってはならないことです。
読み込み時には、トランザクションログにその TVar の書き込み履歴があれば、Ractor local なその最新の値を返し、なければ読み込む。このとき、TVar に記録された最終書き込み時間と、開始時に記録した T を比較し、T が古ければればロールバック。新しければ読み込み完了だが、ついでにトランザクションログに載せておく(次の read 時は、TVar を読む必要がなくなる)
(3) コミット
コミット時、トランザクションログに記録された TVar たちについて、最終書き込み時間が T より新しくないことを確認
時刻を1進める。この時の時刻を T'とする。
書き込みが必要な TVar には、変更を反映。このとき、その TVar の最終書き込み時間が T'となる。