UX仙台 デザイナーが大事にしていること に行ってきました
2014/11/15のUX仙台のレポート。
チームで協業するための、共有のしかた (フジタジュンコさん)
過去
デザインチームが話を聞いた時点で開発期間はもう決まってたりする。
前工程で遅延が発生してても、〆切は変わらない。
デザイナーはその短い期間内で徹夜してでも頑張って終わらせる。
なのに、
「この通りに作ってくれればいいから」と言ってたのに、出来上がったら
「何か違うんだよねぇ。。」と言われたり、
全部作り終わって、テストに入ろうという時になって、
「偉い人がダメって言うから直して」と言われたりする。
死にたい...
なぜこうなる?
「この通りに作ってくれればいいから」の何が悪いかというと、意図を全く伝えていない。
そうなるとアウトプットもいいものが出来ない。指示が明確でないものをデザイナーは表現できない。
お客様に検証するための指標がない。
指標がないので、偉い人が「これじゃダメだよ」と言った事をそのままデザイナーに返す。
どうすれば?
全体を見ることが必要。
Webを作るといった時、製作者はWebだけ考えて良い物を作ろうとしてしまう。
ユーザは「何かをしたい」「何か欲しい」ためにWebを触る。
ユーザに価値を提供する媒体としてWebがあり、ユーザはWebではなく「Webを通して得られる体験」にお金を払う。
全体の流れの中にWebがあることを理解する必要がある。
ユーザに届ける価値が一番大事。
ユーザが「お金を払っても良い」と思える価値は何か?
チームでコンテクストを共有する。
全体でコンテキストを共有しないと最高のものを提供することはできない。
全体を見て協業するためにやってきたこと
インセプションデッキ
良かった点
- コンパクトなので、共有がすばやくできる。
- キャッチフレーズを作ると、Webサイトでそのまま使える。
- ミーティング1~2回で作れる
悪かった点
- どの程度具体的にすればよいのかが難しい。
- 大きいプロジェクトだと、内容がざっくりすぎて、何回も作る羽目になった。
大規模なプロジェクトでは難しいが、中規模、小規模のプロジェクトでは有用。
インセプションデッキを使用することで、ユーザが何を求めているか?
我々のサービスがどうアプローチ出来るかを共有する事が出来る。
ビジネスモデルキャンバス
良かった点
- チーム外の活動(営業、マーケットの動き、エバンジェリストの活動等)を洗い出す事ができた。
- 顧客にもたらす価値を共有できた。
- コミュニケーションのポリシーまで決められた。
悪かった点
- 時間がかかる。(以前のプロジェクトでは1週間くらいかかった)
デザインコンセプトを作る時には有効。
他にカスタマージャーニーマップも参考になる。
ペーパープロトタイピング
UI設計書の前にペーパーでプロトタイピングしたもの。
UIするデザイナーとのやりとりに使っていた。
社内でテスターの人を呼んでペーパーで使い勝手をテストしてもらう。
良かった点
- ペーパーで一旦テストしているので、後で「この画面使えない」という事はなかった。
- テストとデザインを別立てで走らせられる。デザイナーを待たせないで済む。
悪かった点
- 工作に時間がかかる。
- 学習コストが高い。
- 紙は動かないので、インタラクション設計を別にする必要がある。
画面遷移がそれほど多くないものであれば、紙に書いたものをデザイナーに渡すだけで、デザイナーは割とやれる。
気を付けている事
全体を見て共有する事によるメリット
プロジェクトが始まった時点ではみんな「よいもの」を作りたいと思っている。
職歴、立場等で、「よいもの」がずれてくる。
「ユーザにとって」よいものを作るために上記の手法は有用。
デザインワークをスムーズにする、共有のしかた (井上亜津奈さん)
デザインはふわっとしたモノととらわれがち。
なぜこのデザインにしたか、デザイナーは常に答えを用意しておかなければいけない。
クライアントは説明の方にお金を払っている。
デザインの目的は課題の把握と解決。
デザインで何ができるかを考えて、具体的な方法を提示する。
主観やセンスで片付けられるものではない。
デザインの現場でよくある事
でも、お客さんは悪くない。当たり前の事。
お客さんに分かりやすいものを提示して誘導するのもデザイナーの仕事。
明確なイメージを一緒に探らなければ迷路に迷い込んでしまう。
まず、ビジネスやサービスの目的を理解すること。
デザインに出来ない事も理解しておくこと。
「検索で常に上位に来る」とかはデザインでは出来ない。
トーン&マナーを作る
トーンとは、企業やサービスがもってる「らしさ」の事。
トーン(概念)をルール化したものをトーン&マナーという。
トーン&マナーを作るときはターゲットが大切。
デザインの前に表現の方向性の共有を行う。これでデザインが楽になる。
ヒアリングでは世界観を表すものだったら何でも(雑誌、サイト、ブランド等)いいので聞く。
ユーザのヒアリングに有効な手法
言語マトリクス
ユーザヒアリングの中で出てきた抽象的なワードを拾って言語マトリクスを作成し、どのあたりを狙うかを考える。
競合の会社がマトリクス上どのあたりに位置するかをポイントし、そのあたりは外すという事も出来る。
お客さんも参加しやすいというメリットがある。
カラーリング
こんなキーワードで、こんなターゲットで、こんな組み合わせはどうでしょう? と提案する。
カラーは世界を決めてしまうものなので大切。
イメージボード
世界観を表す画像を集めたスクラップブックのようなもの。
手間はかかる。
お客さんとイメージ共有を手っ取り早くするために使用する。
サンプルサイト
見えるものに引っ張られてしまうという弊害がある。
見せたサイトと全く同じものを作ると思われたり、イメージではなくガワの話に終始する危険性がある。
使えるかはお客さんに依る。
ワークショップ
2チームに分かれてのデザインワークショップ。
自分のチームは、ずんだPRのためのWebサイトリニューアル企画検討。
(課題内容書いた紙を持ち帰るの忘れた orz)
ワークショップの内容
- 依頼内容の確認。
- 依頼内容を元にお客様にヒアリング。(ヒアリングシート無くしたので写真なし。)
- ヒアリングした内容から、企画検討。
- プレゼンのボードを作成し、お客様(相手チーム)にプレゼン。
企画検討
良かったところ
- 企画検討のとき、アイデアを各自が発言しながら付箋に書いて貼っていくのは良かった。(誰か一人が記載するより早い。人の発言を聞きながら自分の手を動かせる)
- 企画検討で、対象の情報をググって、アイデアのヒントになる情報を探すのは有効。(「ずんだは仙台七夕あたりからお盆までが繁忙期」という情報から、七夕祭りとファッションショーを関連付ける案が出た。)
- いまいちだな、と思ってもとりあえず出してみる。他の人がそのアイデアを膨らませてくれるかも。
- アイデア出しのとき、「否定しない」事は大事。人のアイデアで「いまいちだな」と思っても、「このアイデアの活かせそうなところは。。」という方向で考えて、アイデアを発展させることが出来た。
悪かったところ
- お客さんへのヒアリングが難しかった。抽象的なイメージを聞くためにどんな質問したら良いか分からなかった。今ブロク見返して、言語マトリクスとか使ってみても良かったと思ったり。
自分のチームで作成したプレゼンボード。
作成した後に思ったのが、「ユーザの発言全てに応えようとはしなくて良い」事。
お客様の色々な立場の人が、それぞれの立場から意見を出してきたら、その中には相反する要望、実現出来ない要望、単なる思いつきの要望も入ってるはず。
なので、要望全てに応えようとするのではなく、要望をヒントに方向性を決めて、その方向性に沿って提案をする事が大事。
自分も含めて、プログラマーは言われたことは全て実現しないといけないという考えになりがちなので、この視点は意識しないといけない。
デザインにもアジャイルの手法が有効な事が分かったのは面白かった。
- 作者: Jonathan Rasmusson,西村直人,角谷信太郎,近藤修平,角掛拓未
- 出版社/メーカー: オーム社
- 発売日: 2011/07/16
- メディア: 単行本(ソフトカバー)
- 購入: 42人 クリック: 1,991回
- この商品を含むブログ (249件) を見る
IntelliJでスネークケースとキャメルケースの切替
IntelliJで、変数のsnake_case⇔CamelCaseの切替(Eclipseだとctrl+alt+kで出来る)をどうやってやればよいか調べたのでメモ。
Plugin入れないとダメらしい。
試した環境は、IntelliJ IDEA 12.1.6、Windows7 Professional。
導入手順
- JetBrains Plugin Repository :: CamelCaseをインストールしてIntelliJ再起動。
- 切替する単語にカーソルのせて、Shift+Alt+uで切替。押す度に切り替わる。
is_readable という単語を切り替えてみると、
- IS_READABLE
- IsReadable
- isReadable
- is_readable
というように切り替わる。
Pythonのデフォルト引数にハマった
Python初心者がハマるポイントにしっかりハマってしまったので記録残しとく。
Pythonの関数引数にはデフォルト値が指定できる。
時刻指定する関数で、引数省略時は現在時刻を取得する関数を作ろうとした。
import time def tim(t=time.time()): print(t) if __name__ == "__main__": tim() time.sleep(1.25) tim() time.sleep(1.4) tim()
が、このtim関数を実行すると、同じ時間しか取得されない。
C:\>python.exe time_func_test.py 1414077198.46061 1414077198.46061 1414077198.46061
このデフォルト引数の値はモジュール評価時に決まるようで、一度関数を実行すると、後は決まった値が使用され続けるらしい。
上記の関数ではやってないけど、デフォルト引数にリストなどの可変オブジェクトを渡して、処理内でリストを更新すると、次の関数コール時は更新したリストが渡されてしまう。
元々やりたかった処理は、以下のような処理で実現した。
import time def tim(t=None): if t is None: t=time.time() print(t) if __name__ == "__main__": tim() time.sleep(1.25) tim() time.sleep(1.4) tim()
実行結果。毎回値が変わるようになったのでやりたいことは出来たっぽい。
C:\>python.exe time_func_test.py 1414078025.345443 1414078026.595514 1414078027.997595
ExpectedExeptionを使ってみる
JavaでExceptionが発生する事を検証するテストは以下のように書く事が出来る。
@Test(expected = IllegalArgumentException.class) public void Exceptionの検証() { throw new IllegalArgumentException("このメッセージは検証できない"); }
ただ、このテストケースでは、意図したExceptionが発生する事は検証できるが、そのExceptionの内容の検証を行う事が出来ない。
Exceptionの検証にExpectedExceptionクラスが使えるという情報を貰ったので使い方を確認。
使い方
サンプル
import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static org.hamcrest.Matchers.endsWith; public class ExpectedExceptionTest { // @Ruleを付加するフィールドはpublicフィールドとする @Rule public ExpectedException expected = ExpectedException.none(); @Test public void ExpectedExceptionを使ったException検証() { // 検証する内容を設定 expected.expect(IllegalArgumentException.class); expected.expectMessage("sample"); // 部分一致検証 expected.expectMessage(endsWith("exception")); // Exceptionが発生する処理を実行 new SampleClass(null); } static class SampleClass { SampleClass(String str) { if ( str == null ) { throw new IllegalArgumentException("sample exception"); } } } }
このコードで、Exception#getMessage()の内容を検証する事が出来る。
なお、ExpectedException#expectMessage(String)メソッドは部分一致の検証である事に注意。
JUnit実践入門にも載ってた。
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (67件) を見る
TDDBC仙台 4thに行ってきた
昨年に続きTDDBC仙台に参加してきました。
@t_wadaさん、スタッフ、および参加者の皆さん、どうもありがとうございました。
当日のtogetter
TDDBC仙台 4th - Togetterまとめ
基調講演
@t_wadaさんによる基調講演。
講演資料は後日SlideShareにアップするとの事。
TDDの目標
動作するキレイなコードがTDDの目標。
テストコードが存在することで、改善が可能になる。
ソフトウェア工学は予測性が低い。コードを書き始めることで初めて問題に気がつくことが多い。
なので、完璧な設計をしてから実装を始めるのではなく、とりあえず実装し、きれいなコードにするのはその後にする。TDDはその手助けをしてくれる。
TDDのサイクル
- 目標を示すテストコードを書く
- テストを成功させるプロダクションコードを書く
- プロダクションコードをリファクタリングする
このサイクルが重要。
納期等の理由でリファクタリングされなくなる事があるけどちゃんとやりましょう。
テストコードのメンテナンスコストが高いと、「テストコード修正が大変だからプロダクションコードの改造はしない」という本末転倒な事になる。
なので、テストコードにもリファクタリングが必要。
プロダクションコードと同じく、テストコードもDRYにする。コピペしまくったりとかやってはいけない。
TDDを導入することで、実装時間は2割弱増えるが、その後の不具合検出数が減るため、トータルでの開発工数は減る。
TDDの対象範囲
テストの目的は対象によって異なる。
TDDはDeveloper Testing。開発者の開発促進が目的。
TDDの"T"はTestというよりCheckingに近い。(TDDはプロダクションコードが動作する事の確認でしかないため)
要件のテストとして、BDDという方法も出て来ているが、RSpecを使用したBDDとScenarioを使用したBDDの二派に別れているのが現状。
テストを動かすだけでは品質は上がらない。テストは品質を明確にするだけ。
品質を高めるのはリファクタリング。
体重計に乗っただけでは体重は減らないのと同じ。
なぜTDDをするか
「動いているコードは触るな」とは今は言えない。
コードを触らなくても周り(OS、フレームワーク等)が変わっていく。直さざるを得ない。
何もしなければコードは腐っていく。
テストコードがあることで、コードへのフィードバックが即座に得られ、書いたコード、これから書くコードに自信が持てる。
TDDライブデモ
@135yshrさんと@nnasakiさんによるgo言語によるライブデモ。
go言語は全く知らなかったけど、@135yshrさんのIDE(Sublime text)の使いこなしに感心したり、「テストコード書くのにコピペは使わないようにしてる」の発言になるほどと思ったりしながら見させて頂きました。
昼飯
はらこめし。うま。
ペアプロ
昨年に続きJavaで参加。
Pythonやりたかったけどペア居なかった。。
課題と自分らのチームで実装したソース
QAとか
和田さんへの質問と回答。
Q) 基調講演の中で、プロダクションコードとテストコードの比率が同じになるような例が出ていたが、テストコードはそんなに減らせるものなのか?
A) プロダクションコードとテストコードの比率が1:1になるようにリファクタリングし、プロダクションコードとテストコードの比率が1:1~1:2の範囲に収まるようにする。
この比率を超えるようならテストコードの見直しが必要。
Q) テストコードで、Mockは使うべきか、使わないべきか?
A) Mockは使う派と使わない派がいるが、@t_wadaさんはMock使わない派。
実物のコードが使えるなら、出来るだけ実物を使うようにしている。実物のコードを使うことで時間がかかるのは仕方ないと考えている。
最後に
テスト書きましょう。
pipのインストール(Python3.3 Windows7)
Windows7の環境でpipをインストールしたときのメモ。
参考:python - How to install pip on windows? - Stack Overflow
手順
get-pip.pyをダウンロード
https://raw.github.com/pypa/pip/master/contrib/get-pip.py
からget-pip.pyをダウンロードして保存する。拡張子は.pyで。
コマンドプロンプトからget-pip.pyを実行
なお、Pythonのインストール先をProgram Files配下にしている場合、コマンドプロンプトは管理者権限で起動する必要がある。
C:\>python C:\Downloads\get-pip.py Downloading/unpacking pip Downloading/unpacking setuptools Installing collected packages: pip, setuptools Successfully installed pip setuptools Cleaning up...
これで、Pythonインストールディレクトリ下のScriptsディレクトリにpipコマンドがインストされる。
PATH環境変数にこのディレクトリを追加しておけば、コマンドプロンプトからpipコマンドが実行出来る。
(後日追記)
Proxy配下でのpipインストール
Proxy配下の環境では上記手順だとインストール出来なかった。
公式のPythonの代わりにnimicondaをインストールした環境で、以下手順でProxyの設定を行ってインストールした。
C:\>set HTTP_PROXY=http://ID:PASS@PROXY-HOST:PROXY-PORT C:\>conda install pip Fetching package metadata: .. Solving package specifications: . Package plan for installation in environment C:\Programs\Miniconda3: The following packages will be downloaded: package | build ---------------------------|----------------- conda-3.7.1 | py34_0 201 KB pip-1.5.6 | py34_0 1.3 MB requests-2.4.3 | py34_0 607 KB setuptools-5.8 | py34_0 738 KB ------------------------------------------------------------ Total: 2.9 MB The following NEW packages will be INSTALLED: pip: 1.5.6-py34_0 setuptools: 5.8-py34_0 The following packages will be UPDATED: conda: 3.7.0-py34_0 --> 3.7.1-py34_0 requests: 2.4.1-py34_0 --> 2.4.3-py34_0 Proceed ([y]/n)? y Fetching packages ... conda-3.7.1-py 100% |###############################| Time: 0:00:01 160.19 kB/s pip-1.5.6-py34 100% |###############################| Time: 0:00:04 328.15 kB/s requests-2.4.3 100% |###############################| Time: 0:00:01 501.35 kB/s setuptools-5.8 100% |###############################| Time: 0:00:08 84.44 kB/s Extracting packages ... [ COMPLETE ] |#################################################| 100% Unlinking packages ... [ COMPLETE ] |#################################################| 100% Linking packages ... [ COMPLETE ] |#################################################| 100%
pip実行する時もproxy指定が必要。
C:\>pip install pycrypto --proxy=http://ID:PASS@PROXY-HOST:PROXY-PORT
参考:
Windows & プロキシ配下でPythonを開発する君へ - Qiita
Installing Python's easy_install using ez_setup.py from behind a proxy server - Stack Overflow
pythonメモ proxy下でのpip、easy_install - Qiita
Java8 lambda式の復習
桜庭祐一さん(@skrb)のlambda式 ハンズオンの復習を兼ねてまとめ。
ハンズオンの教材:
skrb/LambdaDojo · GitHub
lambda式を使うメリット
lambda式を使用する意味は、処理のパラレル化。
処理を複数CPUで並列に処理することで、高速化を図る事が出来る。
Functional Interface
lambda式はfunctionを書くもの。
stream apiはiteratorを書くもの。
lambda式は、Functional Interfaceのメソッドをimplementsする。
Interface | method |
---|---|
Runnable | void run() |
Callable |
T call() |
Comparator |
boolean compare(T t1, T t2) |
メソッド名を覚えておく必要はあまりなく、引数と戻り値さえ覚えておけばOK。
他にも、java.util.function packageに色々ある。
Functinal Interfaceを自作する場合は、@FunctionalInterfaceアノテーションを付与すると、コンパイラがFunctionalなInterfaceになっているかをチェックしてくれる。
演習
Functional Interfaceの実装
基本的なlambda式の書き方から。
Comparator<Integer> comparator1 = new Comparator<Integer>() { @Override public int compare(Integer x, Integer y) { return x - y; } };
ComparatorはFunctinal Interface。
このComparatorの無名クラスの定義は以下のように書き換えられる。
Comparator<Integer> comparator1 = (x, y) -> x - y;
なお、引数の型省略は、片方のみ(例えば(x, Integer y)のような)は出来ない。型省略時は両方の引数の型を省略する。
余談として、Intellijでは、特に操作しなくてもラムダ式に書き換えられる。
引数がない場合のlambda式の記述方法。引数指定がない場合でも()は省略不可。
Callable<Date> callable1 = () -> new Date();
下記のような記載も可。(コンストラクタリファレンス)
Callable<Date> callable2 = Date::new;
ループ処理
List<String> strings = Arrays.asList("a", "b", "c", "d", "e"); StringBuilder builder = new StringBuilder(); for (String s: strings) { builder.append(s); }
forループは、forEachメソッドでのループに置き換える。
List<String> strings = Arrays.asList("a", "b", "c", "d", "e"); StringBuilder builder3 = new StringBuilder(); strings.forEach(t -> builder3.append(t));
メソッドコールをメソッドリファレンスの形式にすると、引数の記載も不要。
List<String> strings = Arrays.asList("a", "b", "c", "d", "e"); StringBuilder builder4 = new StringBuilder(); strings.forEach(builder4::append);
filterメソッド
List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); for (Integer x : numbers) { if (x % 2 == 0) { System.out.print(x); } } System.out.println();
forはforEachに、elseを持たないifはfilterに置き換え。
numbers.stream(). filter(x -> x % 2 == 0). forEach(System.out::print); System.out.println();
数値計算
List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); double ave = 0.0; for (Integer x : numbers) { ave += x; } System.out.println(ave / numbers.size());
forEachメソッドを使用して置き換え。
なお、無名クラスと同様に、finalでないローカル変数にはラムダ式からはアクセス出来ない。ave2変数はクラス変数とする必要がある。
// ave2はクラス変数 numbers.forEach(x -> ave2 += x); System.out.println(ave2 / numbers.size());
数値の合算をreduceメソッドに置き換え。(reduceは値を集約するメソッド。第一引数に初期値、第二引数に値集約を行うラムダ式を指定する)
int ave3 = numbers.stream() .reduce(0, (x, y) -> x + y); System.out.println(ave3 / numbers.size());
Integer<->intのオートボクシング回数を削減するため、mapToIntでintに置き換えを行い、Integerへのオートボクシングを抑止。
int ave4 = numbers.stream() .mapToInt(x -> x) .reduce(0, (x, y) -> x + y); System.out.println(ave4 / numbers.size());
単純な合計値計算は、sumメソッドで出来る。
int ave5 = numbers.stream() .mapToInt(x -> x) .sum(); System.out.println(ave5 / numbers.size());
指定回数分の実行
// 乱数のリストを作成 Random random = new Random(); List<Double> numbers = new ArrayList<>(); for (int i = 0; i < 100; i++) { numbers.add(random.nextDouble()); }
forループをIntStream.range関数に置き換え。range関数は、指定した数値の範囲(回数)だけ処理を行う。
List<Double> numbers2 = new ArrayList<>(); IntStream.range(0, 100) .forEach(i -> numbers2.add(random.nextDouble()));
予め空リストを作って値を追加しくていくのではなく、collectメソッドでコレクションに変換する。collectメソッドの引数にList変換関数を指定する事で、Listへの変換を行う。
List<Double> numbers3 = IntStream.range(0, 100) .mapToObj(i -> random.nextDouble()) .collect(Collectors.toList());
桜庭さんのIT Proの連載。
Java技術最前線 - Java技術最前線:ITpro