2016-12-17

英単語の4択問題に学習効果なし スマホUXの話を添えて

最近また英単語の学習を始めた。
過去数回、手を出しては飽きての繰り返しだったけど、
今回は本気だ。

で、なんか英単語の学習アプリ入れようと思って
いろいろなアプリを試してしてきたんだけど、なかなかグッとくるものが少ない。なかなか学習効果がでない。なんでだろうなと思って考えてみると、次の3点の理由が見えてきた。

4択問題は、英単語と意味を関連づけられない。

あなたは、次の4択問題に答えられるだろうか?

articulateの意味は?
A. 連結式の
B. 人工物
C. はっきりと話す
D. つり上げる

正解はC。なかなか難しい単語だ。
でも、何度かこの問題に出会うと、いつしか単語ではなく選択肢だけを見ても、
なんか C が正解だったような…?というのがわかってしまう

その瞬間、もはやarticulateという単語とその意味の結びつきには意識がいっていない。
それでなにが困るかっていうと、

I can't articulate about the problem.

とか、実際の英文で英単語をみたとき、意味を思い出せないことがけっこう多い。
アプリでは正解しまくっている単語なのにだ
選択肢があればわかるのに…では意味がない。

アプリ起動時のたった数秒の待ち時間で、モチベーションは恐ろしいほど下がる。

多くのアプリでは、学習開始までにけっこう時間がかかっていることに気がついた。

起動画面

ホーム画面

学習コースを選択

学習開始

ここにお知らせ画面やダイアログなどが挟まるケースもあり、
実際に学習を開始できるまでには起動から数秒 〜 ゲーム系に至っては30秒ほどもかかることさえある。

この時間を待てるかどうかは個人によるだろうけど、
自分の場合、時間潰しや楽しみのためのアプリであれば、待てる。
でも、勉強しようという、必要に迫られて…の要素が混じった気持ちでは、待てない。

なぜなら、スマホの中はコンテンツで溢れかえっている。にもかかわらず学習のためのアプリを起動しようという気持ちは、レアで瞬間的なものだからだ。
機を逃したらあっという間に冷めていってしまう。

意味はひとつじゃない

そんなわけで、4択問題式じゃなく、なおかつ起動1秒で学習を始められるアプリが必要なんだけど、実はもう一個注文がある。
大体のアプリでは、英単語と和訳が1:1なのだ。
「culture」 だったら、「文化」という和訳しか表示されなかったりする。だけど、cultureには「栽培」という意味もあって、そーゆー意味もおさえたい。

以上の要望を満たしてくれるアプリが見つからない、ならば作ればいいということで、シンプルなアプリをリリースした。

Usagi 英単語 

https://play.google.com/store/apps/details?id=tec.hie.la.usagi
https://play.google.com/store/apps/details?id=tec.hie.la.usagi

アプリ自体の使い方を学ぶことが必要なUIは、ユーザに優しくない
出来る限りシンプルであることを目指して作ったので、メニューも最低限だ。
やる気の瞬間を逃さないように、起動してすぐに学習を始められるサクサクアプリであり、少なくとも自分自身はヘビーユーザになれる作り。
(単語の発音もしてくれるし)

ここに書かれていない開発の動機もあり、それはまた次回書こうと思う。
では!


 

https://play.google.com/store/apps/details?id=tec.hie.la.usagi



https://play.google.com/store/apps/details?id=tec.hie.la.usagi

https://play.google.com/store/apps/details?id=tec.hie.la.usagi


2016-12-12

男に結婚を意識させるには、IKEAデートがベストだと思う

恐怖!買った憶えのないゼクシィがテーブルに

結婚になかなか踏み切らない男へのプレッシャーとしてよく聞く話だけど、
本当にこんなことあるんだろうか。
結婚プレッシャーとしてのゼクシィは逆効果にしかならないと思う。
人間心理として、押し付けられるものからは距離をとりたくなる(あるいは反発したくなる)ものだし、
人生の大事な決断を、相手に急かされてしてしまった、という形も嫌であり、
その結果、彼が「自分で決断をした」と思える時期まで結婚話は塩漬けにされることになる。
それによってますます結婚が遠のいてしまい、なんだったら3回目のゼクシィでは別れを切り出されてもおかしくない。


男に結婚を意識させたければ、いちばんにIKEAデートをおすすめする。なぜならば。


1.押し付けられるのではなく、自発的に結婚生活を意識できる。

IKEAに行くとたくさんの家具が展示されている。
しかし、それらはただ展示されているわけではなく、
さまざまなコンセプトのモデルルームの一部として展示されており、
どれもオシャレな部屋として完成された見せ方となっている。
それゆえ、こんな部屋に住みたいなという、新しい生活をポジティブにイメージしやすい。
家具のラインナップも、シングルよりもカップルや、子供もいるファミリー層向けのものが多く、自然と「隣りにいる恋人との新しい生活」をイメージすることになる。



2.客層のカップル比率が高く「みんなそうしてる」という空気がある

友達や同級生の結婚話が増えるにつれて、そろそろ自分も…という話はよくある。
周囲がそうしているから自分も、という心理は、それが無意識的なものだとしても、
かなり強い力を持っている。
IKEAのカップルやファミリーな客層、雰囲気は、結婚の決断へむけて彼の背中をぐっと押してくれる。


3.結婚プレッシャーですよの露骨さがない

普段のデートの一環で自然に行ける。
オシャレな、センスのいい、かわいい家具や雑貨のウィンドウショッピングのノリで行ける気軽さがある。
気軽に行けるのに効果大であり、ローリスクハイリターンなデートコースといえる。
店内にレストランもあり、一日を過ごすにも充分な店舗規模と品揃えだ。
とても家に置けないような大きな家具だって、見てるぶんにはなかなか楽しい。
これより効果的で、自然で、心理的反発のない結婚プレッシャーのかけ方は、ちょっと思いつかない。


ゼクシィは結婚の意識が醸成された後に、あくまでもキッカケとして

無理に結婚を迫って彼の心が離れていくよりも、
自発的に彼の心が結婚に向くように仕向けるのが、急がば回れ的な近道であり、
お互いに「この人と結婚するんだな」って気持ちが湧いてきたタイミングでゼクシィでも買ってくれば、 彼も結婚を決断するか、または今すぐとはいかなくても、前向きで具体的な話ができると思う。

じゃあそのためにどうしたらいいかっていう手段が、ネット上にあまり刺さるものがなかったので今回この文章を書いたのであり、
IKEAからお金は1円も貰っていない。(この記事の要点)
今ほしいやつ

2016-09-01

単純なルール1つでチームにテストコード文化が根付いた話


テストコード文化が失われていくまで

かつてはテストコードをがっつり書いていたそのチームから、
テストコード書こうぜ!を推進していたメンバーが去っていった。

いつしか新規テストコードは書かれなくなり、
既存のテストもメンテナンスされなくなり、
テスト失敗も放置され、テストコードの価値は地に落ち、
完全にテストコード文化は失われていた。

(類似例)JenkinsおじさんがいなくなるといつしかCIが回らなくなる

品質担保は目視テストのみ!

なぜ、テストコード文化が失われていったのか

問題を整理してみると、原因と思しきものがいくつか見えてきた。
  • テストファーストだと要件定義に振り回されすぎ
    => 特に序盤は。
    => 安定して書けるのはユーティリティクラスくらい
  • コードカバレッジ目標
    => 実装相応の量のテストコードを書かなくてはならない
    => 時間的に書ける人と書けない人がでてくる
    => 書いたとしてもチームとしての取り組みになっていない
  • リリース後に時間とってドキュメントやテストコードを書こう 
    => 結局時間を取らない・取れない・書かない
    => 書いたとしても日々の習慣としてではない 

テストコード文化を取り戻した単純な1つのルール

それは、

プルリクエストには、少なくとも1つ以上のテストコードが無くてはならない。

これだけだ。
実装に問題がない場合でも、テストコードが1件なければ、マージすることはできない。
逆に言うと、1つでもテストコードがありさえすればOKとした。

なぜ効果があったのか?

本ルールは、以下のような視点で設定した。

  • どうしたら最初の1歩を踏み出しやすいか?
  • どうしたら負担なく継続できるか?
  • どうしたら頻繁にテストコードを書く日常を作れるか? 

また、最初から及第点を目指さず、以下のことはバッサリ切り捨てた。

  • テストファーストでなくていい。
  • 全ての実装にテストを書こうとしなくていい。
  • コードカバレッジを追わなくていい。

今回の件では、ルールは緩いよ、でも強制よ、という「緩い強制」による習慣づけと意識改革が功を奏したように思う。

コードカバレッジガバガバやんけ!

テストを書くことが習慣化され、チームの文化として根付いてくると、
いつしか、テストを書かないことに違和感を感じだすようになる。

最初はプルリクエストを通すためにテストコードを1件添えるだけだったのが、
次第に、実装量や影響範囲に比例したテストコードの件数になってきた。

依然としてルール上では、少なくとも1件のテストコードがあればOKなのにもかかわらずだ。

まとめ

個々人のレベルは高くても、チームとしてのレベルが1では良い仕事はできない。
というかそれに根ざした問題ってけっこうありそうな気がする。。。

チームのレベルが上がってはじめて打てる手なんかもあるだろうし、
でもそのわりにチームとしてのレベル、機能してる具合、熟成度ってあんまり計測されてなかったりするし、それに対する評価にいたっては更になされてないように見える。

良いチームで働いてハッピーになろう。

2016-02-23

SpringBoot, JPA: FulltextSearch(NativeQuery) with MySQL


How to implement "MATCH AGAINST" NativeQuery on SpringBoot, JPA, and MySQL

I have to implement simple fulltext search function with MySQL fulltext search. (it is not enough to use any search engine like Solr)
but JPA(hibernate)  can't understand MATCH AGAINST statement, so I've implemented it by using native query.


The hard things of Native Query

  • it cannot use named parameter (:condition) for query declaration.
    only can use sequential placeholder like ?1, ?2 .....
  • it cannot return the data as Page type. to return the data as Page type,
    we have to create PageImpl instance manually like following.
  • we have to create limit statement manually.

Environment

  • Spring Boot 1.3.1
  • JPA(hibernate)
  • MySQL5.7.10 
  • Design: Domain-Driven


implementation

SearchServiceImpl: call findByKeyword() via repository for execution fulltext search

/**
 * execute fulltext search
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    SampleRepository sampleRepository;

    /**
     * full text search by user-inputted keyword
     *
     * @param keyword Search keyword
     * @return Matched result
     */
    public Page<MethodEntity> searchByKeyword(String keyword, Pageable pageable) {

        // parse keyword and get search condition for match against(…) statement
        String searchCondition = getFulltextSearchCondition(keyword);

        List<SampleEntity> sampleEntities = sampleRepository.findByKeyword(searchCondition,
                pageable.getOffset(),
                pageable.getPageSize());

        // avoid entityManager.createNativeQuery(sql).getSingleResult();
        // getSingleResult returns Object type.
        // map result to DTO(Entity)
        CountDto countDto = sampleRepository.countByKeyword(searchCondition);

        // we have to instantiate Page object from List of entity manually
        Page<SampleEntity> page = new PageImpl<>(sampleEntities, pageable, countDto.getCount());

        return page;
    }



SampleEntity: declare native queries

/**
 * Sample Entity
 */
@Entity
@Table(name = "sample")
@Getter // lombok
@Setter // lombok
@NamedNativeQueries({
        // for search
        // impotant: should conform name to method name in SampleRepository
        @NamedNativeQuery(name = "SampleEntity.findByKeyword",
                query = "SELECT s.sample_id, s.body FROM sample AS s WHERE MATCH(s.body) AGAINST(?1 IN BOOLEAN MODE) limit ?2, ?3",
                resultClass = MethodEntity.class),
        // for count
        @NamedNativeQuery(name = "SampleEntity.countByKeyword",
                query = "SELECT count(*) as count FROM sample AS s WHERE MATCH(s.body) AGAINST(?1 IN BOOLEAN MODE)",
                resultClass = CountDto.class)
})
public class SampleEntity {
    @Id
    @GeneratedValue
    @Column(name = "sample_id", nullable = false, unique = true, updatable = false)
    private int id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String body;
               :


SampleRepository: declare methods which are the same name as query name in SampleEntity

/**
 * Sample Repository
 */
@Repository
public interface SampleRepository extends JpaRepository<SampleEntity, Integer> {

    List<SampleEntity> findByKeyword(String keyword, Integer offset, Integer pageSize);

    CountDto countByKeyword(String keyword);
}


CountDto(optional): for specify resultClass to bind result

/**
 * Dto for binding from "select count(*) as count from ..." query
 */
@Entity
@Getter
@Setter
public class CountDto {

    @Id
    private Integer count;
}