良いUIにするにはどうすればいい?

最近、UI関連の仕事をしてたとき、他の人と意見が食い違って、なんでこんなに噛み合ないんだろうと思って何日か考えたら、その人との視点の違いに気がついた。多分、良いUIの要素には少なくとも

  • 操作したらどういう結果になるか、ユーザにとってわかりやすい
  • ユーザが、正しい方法で効率的に、目的を達成できる

の2つがあるんじゃないかと思う。2番目で言うユーザの「目的」というのは、webやPC上で完結しないものを含む。この2つが両立できれば言うことないのだが、場合によっては衝突して両立できないこともあるように思える。例えば機能が増えて複雑になり、1つのフローの中でユーザの選択肢が多くなった場合など。

実際、自分は後者を選択して、そのツールを上手く利用できているユーザの使い方に最適化しようと、機能に制限をかけて画面上の要素も削りまくった。新しい概念が追加されたので、ユーザに多少の学習コストがかかることは仕方ないと考え、その辺はビジネス向けのツールなのである程度は吸収できると考えた。が、ユーザテスト(ユーザビリティテスト)の結果、思いっきり反対意見にあって、修正された。上で言う前者を重視した形になった。

確かに、できたものを前者の視点で見るなら、良くなった。ただ後者の視点で見たときに作業効率が犠牲になった。だから個人的には、ユーザが使い込むにつれて評価が下がるんじゃないかな、という気がしている。もしかしたら、上の2点を両立できる優れたデザイナーがどこかにいるかもしれないけど、今回はできなかった。
(現物は見せらないし、モノが無いと上手く説明できない。抽象的すぎてこれを読んだ人には伝わらなそうだ。。)

今までに無いような新しい概念が追加された場合、作業効率を重視したい場合など、ユーザテストの結果が時間がたつにつれて正しくなくなってしまうこともあるような気がする。ユーザの成長にあわせて適切なタイミングでUIも変えられればいいんだろうけど、一度作ったらしばらく手を入れられない場合、どうしたらいいんだろう。

良いUIにするにはどうすればいい?

Amazon Product Advertising APIの結果をJSONで受け取るスクリプト

(2009/08/11更新)

AmazonがAPIの認証方法を変えたので、このサイトで使ってるスクリプトも変更しなければいけなくなった。今までは、Yahoo! Pipesを使って、AmazonのAPIの結果を XMLからJSONに変換していたが、それができなくなるのでGoogle App Engine で動くスクリプトを書いた。

参考にしたのは以下のサイト(ありがとうございます)。

XMLからJSONへの変換は次のライブラリ(XSLT)を使った(感謝)。

以下がサンプルと手順。

app.yaml

前の(テキスト)エントリーで書いたapp.yamlを修正。次の箇所を変更し、

- url: /(.*.(xml|xsl|xslt))
  static_files: assets/xml/1
  upload: assets/xml/(.*.(xml|xsl|xslt))

以下を足した。

- url: /onca/json
  script: aws_pa.py

xml2json-xslt

xml2json.xsltは、siblings with the same name are not collected into an array if other elements exist at the same level (のコメント11)に添付されているものを使った。
ダウンロードページにあるやつでは、AmazonのXMLを上手く変換できなかったため。

それを上の設定に合わせて asset/xml ディレクトリに入れた。

aws_pa.py

Pythonのコードは以下の通り。

import cgi, urllib, urllib2, hmac, hashlib, base64, re
from datetime import datetime

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class AWSProductAdvertising(webapp.RequestHandler):
    def __init__(self):
        self.secret_key    = "INPUT_YOUR_SECRET_KEY_HERE"
        self.aws_pa_domain = "xml-jp.amznxslt.com"
        self.aws_pa_path   = "/onca/xml"

        self.params = {
            "Service":        "AWSECommerceService",
            "AssociateTag":   "INPUT_YOUR_ASSOCIATE_TAG_HERE",
            "AWSAccessKeyId": "INPUT_YOUR_AWS_ACCESS_KEY_HERE",
            "Version":        "2009-03-31",
            "Timestamp":      datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
            "Style":          "http://YOUR_APP_NAME.appspot.com/xml2json.xslt",
            "ContentType":    "text/javascript"     
        }

    def send_request(self):
        # create query string
        query_strings = []
        for key, value in sorted(self.params.items()):
            query_strings.append(key + "=" + urllib.quote(value.encode('utf-8'), safe="~"))

        query_string = "&".join(query_strings)

        # create signature
        message    = "n".join( ['GET', self.aws_pa_domain, self.aws_pa_path, query_string] )
        digest     = hmac.new(self.secret_key, message, hashlib.sha256).digest()
        signature  = urllib.quote( base64.b64encode(digest) )

        aws_pa_url = "http://%(domain)s%(path)s?%(query)s&Signature=%(signature)s" % {
            "domain":     self.aws_pa_domain,
            "path":       self.aws_pa_path,
            "query":      query_string,
            "signature":  signature
        }

        return urllib2.urlopen(aws_pa_url).read().decode('utf-8')

    def get(self):
        for argument in self.request.arguments():
            if not re.search("^_", argument):
                self.params[argument] = self.request.get(argument)

        result_json = self.send_request()

        callback    = self.request.get('_callback')
        if callback:
            result_json = callback + "(" + result_json + ")"

        self.response.headers['Content-Type'] = 'text/javascript'
        self.response.out.write(result_json)


application = webapp.WSGIApplication( [('/onca/json', AWSProductAdvertising)], debug=True )

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

使い方

下のようなURLをリクエストすると、結果がJSONで返ってくる。

http://YOUR_APP_NAME.appspot.com/onca/json?Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes

また、”_callback” というパラメータを使って、コールバック関数を指定できる。

http://YOUR_APP_NAME.appspot.com/onca/json?Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes&_callback=cbfunc

上のコードはご自由にお使いいただて結構です(連絡も不要)。ただし、このコード、およびその使用によって起こった
いかなる結果についても、私は責任を負いません。

(こういうのは堅苦しくて好きじゃないんだけど、一応。)

追記(2009/06/29): 日本語で検索できなかったのを修正した。

query_strings.append(key + "=" + urllib.quote(value))

の部分を以下のように変更

query_strings.append(key + "=" + urllib.quote(value.encode('utf-8')))

追記(2009/08/11): いろいろ間違ってたので修正

  • SignatureがSigunatureになってたのを修正
  • XSLTを使うには、リクエストをxml-jp.amznxslt.comに送る必要がある(日本以外は XSLT Service URLs を参照)
  • パラメータにContentTypeを追加
  • “/” をエンコードするために、urllib.quote()にsafe=“~”を追加(”~“はエンコードしない)
Amazon Product Advertising APIの結果をJSONで受け取るスクリプト

Script to get JSON from Amazon Product Advertising API

(updated: 2009/08/11)

Since Amazon changed authentication for their API, I have to change my script on this site. I used to use Yahoo! Pipes to convert Amazon, but it’s gonna be obsoleted in August.
Now I wrote script for the alternative solution for it and hosted it on Google App Engine.

I referred to some codes in the following sites (Thank you).

And I used the following library (XSLT) to convert XML to JSON (Thank you too).

The followings are sample code and steps to use it on Google App Engine.

app.yaml

I modified app.yaml file in the previous (text) entry. The following is the modified part.

- url: /(.*.(xml|xsl|xslt))
static_files: assets/xml/1
upload: assets/xml/(.*.(xml|xsl|xslt))

And I added the followings.

- url: /onca/json
script: aws_pa.py

xml2json-xslt

I’m using xml2json.xslt that is attached on siblings with the same name are not collected into an array if other elements exist at the same level (comment 11). The file that is provided in the download page has some problem with Amazon’s XML.

And then I placed it into assets/xml directory according to app.yaml settings.

aws_pa.py

Here is the code.

import cgi, urllib, urllib2, hmac, hashlib, base64, re
from datetime import datetime

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class AWSProductAdvertising(webapp.RequestHandler):
    def __init__(self):
        self.secret_key    = "INPUT_YOUR_SECRET_KEY_HERE"
        self.aws_pa_domain = "xml-us.amznxslt.com"
        self.aws_pa_path   = "/onca/xml"

        self.params = {
            "Service":        "AWSECommerceService",
            "AssociateTag":   "INPUT_YOUR_ASSOCIATE_TAG_HERE",
            "AWSAccessKeyId": "INPUT_YOUR_AWS_ACCESS_KEY_HERE",
            "Version":        "2009-03-31",
            "Timestamp":      datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
            "Style":          "http://YOUR_APP_NAME.appspot.com/xml2json.xslt",
            "ContentType":    "text/javascript"
        }

    def send_request(self):
        # create query string
        query_strings = []
        for key, value in sorted(self.params.items()):
            query_strings.append(key + "=" + urllib.quote(value.encode('utf-8'), safe="~"))

        query_string = "&".join(query_strings)

        # create signature
        message    = "n".join( ['GET', self.aws_pa_domain, self.aws_pa_path, query_string] )
        digest     = hmac.new(self.secret_key, message, hashlib.sha256).digest()
        signature  = urllib.quote( base64.b64encode(digest) )

        aws_pa_url = "http://%(domain)s%(path)s?%(query)s&Signature=%(signature)s" % {
            "domain":     self.aws_pa_domain,
            "path":       self.aws_pa_path,
            "query":      query_string,
            "signature":  signature
        }

        return urllib2.urlopen(aws_pa_url).read().decode('utf-8')

    def get(self):
        for argument in self.request.arguments():
            if not re.search("^_", argument):
                self.params[argument] = self.request.get(argument)

        result_json = self.send_request()

        callback    = self.request.get('_callback')
        if callback:
            result_json = callback + "(" + result_json + ")"

        self.response.headers['Content-Type'] = 'text/javascript'
        self.response.out.write(result_json)

application = webapp.WSGIApplication( [('/onca/json', AWSProductAdvertising)], debug=True )

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

Usage

Just request the URL like;

http://YOUR_APP_NAME.appspot.com/onca/json?Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes

You can specify callback function name with “_callback” parameter. For example,

http://YOUR_APP_NAME.appspot.com/onca/json?Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes&_callback=cbfunc

You can use the above code for free without notification to me. I accept no liability
for the code or for any consequences with the script.

updated (2009/06/29): fix problem on search with multi byte strings. I modified

query_strings.append(key + "=" + urllib.quote(value))

to

query_strings.append(key + "=" + urllib.quote(value.encode('utf-8')))

updated (2009/08/11): fix several problems in the sample code

  • Fix typo. (corrected “Sigunature” to “Signature”)
  • To use XSLT, you need to send request to xml-us.amznxslt.com. (Refer to XSLT Service URLs for other countries.)
  • Added ContentType parameter
  • To encode “/”, added safe=“~” to urllib.quote() (it won’t encode “~”)
Script to get JSON from Amazon Product Advertising API

幸せになりたい幸せになりたいってずっと思ってると
    幸せになりたいってだけで終わっちゃうんです
  幸せだなぁって思ってるとずっと幸せのまま過ぎていくんです

【2ch】ニュー速クオリティ:亀井絵里の人生哲学

(translation)
If you are thinking “I wanna be happy,” time passes, but you still wanna be happy. If you are thinking “I’m happy,” time passes, you’re still happy.

(誰かもっと上手く訳してくれないかな。英語って難しい。)

Quote