STONEDSOUL

positive is not shutting your eyes to the fact

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

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

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

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

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

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

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

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

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](http://code.google.com/p/xml2json-xslt/issues/detail?id=3#c11) (のコメント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 = "webservices.amazon.co.jp"
        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"
        }

    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')))

        query_string = "&".join(query_strings)

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

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

        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)] )

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')))
Page 1 of 34