【转】起步 - 安装 Git
是时候动手尝试下 Git 了,不过得先安装好它。有许多种安装方式,主要分为两种,一种是通过编译源代码来安装;另一种是使用为特定平台预编译好的安装包。
从源代码安装
若是条件允许,从源代码安装有很多好处,至少可以安装最新的版本。Git 的每个版本都在不断尝试改进用户体验,所以能通过源代码自己编译安装最新版本就再好不过了。有些 Linux 版本自带的安装包更新起来并不及时,所以除非你在用最新的 distro 或者 backports,那么从源代码安装其实该算是最佳选择。
Git 的工作需要调用 curl,zlib,openssl,expat,libiconv 等库的代码,所以需要先安装这些依赖工具。在有 yum 的系统上(比如 Fedora)或者有 apt-get 的系统上(比如 Debian 体系),可以用下面的命令安装
yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
之后,从下面的 Git 官方站点下载最新版本源代码:
然后编译并安装:
tar -zxf git-1.7.2.2.tar.gz cd git-1.7.2.2 make prefix=/usr/local all sudo make prefix=/usr/local install
现在已经可以用 git 命令了,用 git 把 Git 项目仓库克隆到本地,以便日后随时更新:
git clone git://git.kernel.org/pub/scm/git/git.git
在 Linux 上安装
如果要在 Linux 上安装预编译好的 Git 二进制安装包,可以直接用系统提供的包管理工具。在 Fedora 上用 yum 安装:
yum install git-core
在 Ubuntu 这类 Debian 体系的系统上,可以用 apt-get 安装:
apt-get install git-core
在 Mac 上安装
在 Mac 上安装 Git 有两种方式。最容易的当属使用图形化的 Git 安装工具,界面如图 1-7,下载地址在:
http://code.google.com/p/git-osx-installer
另一种是通过 MacPorts (http://www.macports.org) 安装。如果已经装好了 MacPorts,用下面的命令安装 Git:
sudo port install git-core +svn +doc +bash_completion +gitweb
这种方式就不需要再自己安装依赖库了,Macports 会帮你搞定这些麻烦事。一般上面列出的安装选项已经够用,要是你想用 Git 连接 Subversion 的代码仓库,还可以加上 +svn 选项,具体将在第八章作介绍。(译注:还有一种是使用 homebrew(https://github.com/mxcl/homebrew):brew install git。)
在 Windows 上安装
在 Windows 上安装 Git 同样轻松,有个叫做 msysGit 的项目提供了安装包,可以到 Google Code 的页面上下载 exe 安装文件并运行:
http://code.google.com/p/msysgit
完成安装之后,就可以使用命令行的 git 工具(已经自带了 ssh 客户端)了,另外还有一个图形界面的 Git 项目管理工具。
本文转载自:http://git-scm.com/book/zh/%E8%B5%B7%E6%AD%A5-%E5%AE%89%E8%A3%85-Git
部分链接地址或许需要翻墙才可以访问
Discuz7.0/UCHome2.0/WordPress Ngnix下伪静态完全兼容
如果web站点根目录先放的是wordpress,bbs目录下放的Discuz7.0,home目录下方的UCHome2.0,以下rewrite或许对你有用
location / { index index.html index.htm index.php; rewrite ^/home/(space|network)\-(.+)\.html$ /home/$1.php?rewrite=$2 last; rewrite ^/home/(space|network)\.html$ /home/$1.php last; rewrite ^/home/([0-9]+)$ /home/space.php?uid=$1 last; rewrite ^/bbs/archiver/((fid|tid)-[\w\-]+\.html)$ /bbs/archiver/index.php?$1 last; rewrite ^/bbs/forum-([0-9]+)-([0-9]+)\.html$ /bbs/forumdisplay.php?fid=$1&page=$2 last; rewrite ^/bbs/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /bbs/viewthread.php?tid=$1&extra=page\%3D$3&page=$2 last; rewrite ^/bbs/space-(username|uid)-(.+)\.html$ /bbs/space.php?$1=$2 last; rewrite ^/bbs/tag-(.+)\.html$ /bbs/tag.php?name=$1 last; if (!-e $request_filename) { rewrite ^.+/?(/wp-.*) $1 last; rewrite ^.+/?(/.*\.php)$ $1 last; rewrite ^(.+)$ /index.php?q=$1 last; } }
顺序也是不能随便的哦。wordpress的伪静态需要放到最下面。
php-5.2 的 php.ini 中文版
适用于 php-5.2 的 php.ini 中文版 作者:金步国 版权声明 本文作者是一位自由软件爱好者,所以本文虽然不是软件,但是本着 GPL 的精神发布。任 何人都可以自由使用、转载、复制和再分发,但必须保留作者署名,亦不得对声明中的任何 条款作任何形式的修改,也不得附加任何其它条件。您可以自由链接、下载、传播此文档, 但前提是必须保证全文完整转载,包括完整的版权信息和作译者声明。 其他作品 本文作者十分愿意与他人共享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣, 可以在如下位置查看现有作品的列表: • 金步国作品列表 BUG报告,切磋与探讨 由于作者水平有限,因此不能保证作品内容准确无误,请在阅读中自行鉴别。如果你发现了 作品中的错误,请您来信指出,哪怕是错别字也好,任何提高作品质量的建议我都将虚心接 纳。如果你愿意就作品中的相关内容与我进行进一步切磋与探讨,也欢迎你与我联系。联系 方式:MSN: csfrank122@hotmail.com ;;;;;;;;;;;;;;;;; ;; 关于php.ini ;; ;;;;;;;;;;;;;;;;; ; 这个文件必须命名为'php.ini'并放置在httpd.conf中PHPINIDir指令指定的目录中。 ; 最新版本的php.ini可以在下面两个位置查看: ; http://cvs.php.net/viewvc.cgi/php-src/php.ini-recommended?view=co ; http://cvs.php.net/viewvc.cgi/php-src/php.ini-dist?view=co ;;;;;;;;;;;; ;; 语法 ;; ;;;;;;;;;;;; ; 该文件的语法非常简单。空白字符和以分号开始的行被简单地忽略。 ; 章节标题(例如: [php])也被简单地忽略,即使将来它们可能有某种意义。 ; ; 设置指令的格式如下: ; directive = value ; 指令名(directive)是大小写敏感的!所以"foo=bar"不同于"FOO=bar"。 ; 值(value)可以是: ; 1. 用引号界定的字符串(如:"foo") ; 2. 一个数字(整数或浮点数,如:0, 1, 34, -1, 33.55) ; 3. 一个PHP常量(如:E_ALL, M_PI) ; 4. 一个INI常量(On, Off, none) ; 5. 一个表达式(如:E_ALL & ~E_NOTICE) ; ; INI文件中的表达式仅使用:位运算符、逻辑非、圆括号: ; | 位或 ; & 位与 ; ~ 位非 ; ! 逻辑非 ; ; 布尔标志用 On 表示打开,用 Off 表示关闭。 ; ; 一个空字符串可以用在等号后不写任何东西表示,或者用 none 关键字: ; foo = ; 将foo设为空字符串 ; foo = none ; 将foo设为空字符串 ; foo = "none" ; 将foo设为字符串'none' ; ; 如果你在指令值中使用动态扩展(PHP扩展或Zend扩展)中的常量, ; 那么你只能在加载这些动态扩展的指令行之后使用这些常量。 ;;;;;;;;;;;;;;;;;; ;; httpd.conf ;; ;;;;;;;;;;;;;;;;;; ; 可以在httpd.conf中针对特定虚拟主机或目录覆盖php.ini的值,以进行更灵活的配置: ; php_admin_value name value ;设置非bool型的指令,将value设为none则清除先前的设定 ; php_admin_flag name on|off ;仅用于设置bool型的指令 ; [提示]因为很多指令不允许使用php_value/php_flag进行设置,因此不建议使用这两个。 ; ; PHP常量(如E_ALL)仅能在php.ini中使用,在httpd.conf中必须使用相应的掩码值。 ;[2008-3-2日更新] ; ================================================================================ ========== ;;=====================================配置指令详解=============================== ========= ; ================================================================================ ========== ; 以下每个指令的设定值都与 PHP-5.2.5 内建的默认值相同。 ; 也就是说,如果'php.ini'不存在,或者你删掉了某些行,默认值与之相同。 ;;;;;;;;;;;;;; ;; Apache ;; ;;;;;;;;;;;;;; [Apache] ; 仅在将PHP作为Apache模块时才有效。 child_terminate = Off ; PHP脚本在请求结束后是否允许使用apache_child_terminate()函数终止子进程。 ; 该指令仅在UNIX平台上将PHP安装为Apache1.3的模块时可用。其他情况下皆不存在。 engine = On ; 是否启用PHP解析引擎。 ; 提示:可以在httpd.conf中基于目录或者虚拟主机来打开或者关闭PHP解析引擎。 last_modified = Off ; 是否在Last-Modified应答头中放置该PHP脚本的最后修改时间。 xbithack = Off ; 是否不管文件结尾是什么,都作为PHP可执行位组来解析。 ;;;;;;;;;;;;;;; ;; PHP核心 ;; ;;;;;;;;;;;;;;; [PHP-Core-DateTime] ; 前四个配置选项目前仅用于date_sunrise()和date_sunset()函数。 date.default_latitude = 31.7667 ; 默认纬度 date.default_longitude = 35.2333 ; 默认经度 date.sunrise_zenith = 90.583333 ; 默认日出天顶 date.sunset_zenith = 90.583333 ; 默认日落天顶 date.timezone = ; 未设定TZ环境变量时用于所有日期和时间函数的默认时区。 ; 中国大陆应当使用"PRC" ; 应用时区的优先顺序为: ; 1. 用date_default_timezone_set()函数设定的时区(如果设定了的话) ; 2. TZ 环境变量(如果非空的话) ; 3. 该指令的值(如果设定了的话) ; 4. PHP自己推测(如果操作系统支持) ; 5. 如果以上都不成功,则使用 "UTC" [PHP-Core-Assert] assert.active = On ; 是否启用assert()断言评估 assert.bail = Off ; 是否在发生失败断言时中止脚本的执行 assert.callback = ; 发生失败断言时执行的回调函数 assert.quiet_eval = Off ; 是否使用安静评估(不显示任何错误信息,相当于error_reporting=0)。 ; 若关闭则在评估断言表达式的时候使用当前的error_reporting指令值。 assert.warning = On ; 是否对每个失败断言都发出警告 [PHP-Core-SafeMode] ; 安全模式是为了解决共享服务器的安全问题而设立的。 ; 但试图在PHP层解决这个问题在结构上是不合理的, ; 正确的做法应当是修改web服务器层和操作系统层。 ; 因此在PHP6中废除了安全模式,并使用基于open_basedir的安全防护。 ; 此部分指令在PHP6中已经全部被删除。 safe_mode = Off ; 是否启用安全模式。 ; 打开时,PHP将检查当前脚本的拥有者是否和被操作的文件的拥有者相同, ; 相同则允许操作,不同则拒绝操作。 safe_mode_gid = Off ; 在安全模式下,默认在访问文件时会做UID比较检查。 ; 但有些情况下严格的UID检查反而是不适合的,宽松的GID检查已经足够。 ; 如果你想将其放宽到仅做GID比较,可以打开这个参数。 safe_mode_allowed_env_vars = "PHP_" ; 在安全模式下,用户仅可以更改的环境变量的前缀列表(逗号分隔)。 ; 允许用户设置某些环境变量,可能会导致潜在的安全漏洞。 ; 注意: 如果这一参数值为空,PHP将允许用户更改任意环境变量! safe_mode_protected_env_vars = "LD_LIBRARY_PATH" ; 在安全模式下,用户不能更改的环境变量列表(逗号分隔)。 ; 这些变量即使在safe_mode_allowed_env_vars指令设置为允许的情况下也会得到保护。 safe_mode_exec_dir = "/usr/local/php/bin" ; 在安全模式下,只有该目录下的可执行程序才允许被执行系统程序的函数执行。 ; 这些函数是:system, escapeshellarg, escapeshellcmd, exec, passthru, ; proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, shell_exec safe_mode_include_dir = ; 在安全模式下,该组目录和其子目录下的文件被包含时,将跳过UID/GID检查。 ; 换句话说,如果此处的值为空,任何UID/GID不符合的文件都不允许被包含。 ; 这里设置的目录必须已经存在于include_path指令中或者用完整路径来包含。 ; 多个目录之间用冒号(Win下为分号)隔开。 ; 指定的限制实际上是一个前缀,而非一个目录名, ; 也就是说"/dir/incl"将允许访问"/dir/include"和"/dir/incls" ; 如果您希望将访问控制在一个指定的目录,那么请在结尾加上斜线。 [PHP-Core-Safe] allow_url_fopen = On ; 是否允许打开远程文件 allow_url_include = Off ; 是否允许include/require远程文件。 disable_classes = ; 该指令接受一个用逗号分隔的类名列表,以禁用特定的类。 disable_functions = ; 该指令接受一个用逗号分隔的函数名列表,以禁用特定的函数。 enable_dl = On ; 是否允许使用dl()函数。dl()函数仅在将PHP作为apache模块安装时才有效。 ; 禁用dl()函数主要是出于安全考虑,因为它可以绕过open_basedir指令的限制。 ; 在安全模式下始终禁用dl()函数,而不管此处如何设置。 ; PHP6中删除了该指令,相当于设为Off。 expose_php = On ; 是否暴露PHP被安装在服务器上的事实(在http头中加上其签名)。 ; 它不会有安全上的直接威胁,但它使得客户端知道服务器上安装了PHP。 open_basedir = ; 将PHP允许操作的所有文件(包括文件自身)都限制在此组目录列表下。 ; 当一个脚本试图打开一个指定目录树之外的文件时,将遭到拒绝。 ; 所有的符号连接都会被解析,所以不可能通过符号连接来避开此限制。 ; 特殊值'.'指定了存放该脚本的目录将被当做基准目录, ; 但这有些危险,因为脚本的工作目录可以轻易被chdir()改变。 ; 对于共享服务器,在httpd.conf中针对不同的虚拟主机或目录灵活设置该指令将变得非常有用。 ; 在Windows中用分号分隔目录,UNIX系统中用冒号分隔目录。 ; 作为Apache模块时,父目录中的open_basedir路径将自动被继承。 ; 指定的限制实际上是一个前缀,而非一个目录名, ; 也就是说"/dir/incl"将允许访问"/dir/include"和"/dir/incls", ; 如果您希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线。 ; 默认是允许打开所有文件。 sql.safe_mode = Off ; 是否使用SQL安全模式。 ; 如果打开,指定默认值的数据库连接函数将会使用这些默认值代替支持的参数。 ; 对于每个不同数据库的连接函数,其默认值请参考相应的手册页面。 [PHP-Core-Error] error_reporting = E_ALL & ~E_NOTICE ; 错误报告级别是位字段的叠加,推荐使用 E_ALL | E_STRICT ; 1 E_ERROR 致命的运行时错误 ; 2 E_WARNING 运行时警告(非致命性错误) ; 4 E_PARSE 编译时解析错误 ; 8 E_NOTICE 运行时提醒(经常是bug,也可能是有意的) ; 16 E_CORE_ERROR PHP启动时初始化过程中的致命错误 ; 32 E_CORE_WARNING PHP启动时初始化过程中的警告(非致命性错) ; 64 E_COMPILE_ERROR 编译时致命性错 ; 128 E_COMPILE_WARNING 编译时警告(非致命性错) ; 256 E_USER_ERROR 用户自定义的致命错误 ; 512 E_USER_WARNING 用户自定义的警告(非致命性错误) ; 1024 E_USER_NOTICE 用户自定义的提醒(经常是bug,也可能是有意的) ; 2048 E_STRICT 编码标准化警告(建议如何修改以向前兼容) ; 4096 E_RECOVERABLE_ERROR 接近致命的运行时错误,若未被捕获则视同E_ERROR ; 6143 E_ALL 除E_STRICT外的所有错误(PHP6中为8191,即包含所有) ; 也可以用2147483647(所有二进制位全为1)打开现在或将来可能出现的各种错误 track_errors = Off ; 是否在变量$php_errormsg中保存最近一个错误或警告消息。 display_errors = On ; 是否将错误信息作为输出的一部分显示。 ; 在最终发布的web站点上,强烈建议你关掉这个特性,并使用错误日志代替(参看下面)。 ; 在最终发布的web站点打开这个特性可能暴露一些安全信息, ; 例如你的web服务上的文件路径、数据库规划或别的信息。 display_startup_errors = Off ; 是否显示PHP启动时的错误。 ; 即使display_errors指令被打开,关闭此参数也将不显示PHP启动时的错误。 ; 建议你关掉这个特性,除非你必须要用于调试中。 report_memleaks = On ; 是否报告内存泄漏。这个参数只在以调试方式编译的PHP中起作用, ; 并且必须在error_reporting指令中包含 E_WARNING report_zend_debug = On ; 尚无说明文档 html_errors = On ; 是否在出错信息中使用HTML标记。 ; 注意: 不要在发布的站点上使用这个特性! docref_root = ;"http://localhost/phpmanual/" docref_ext = ;".html" ; 如果打开了html_errors指令,PHP将会在出错信息上显示超连接, ; 直接链接到一个说明这个错误或者导致这个错误的函数的页面。 ; 你可以从http://www.php.net/docs.php下载php手册, ; 并将docref_root指令指向你本地的手册所在的URL目录。 ; 你还必须设置docref_ext指令来指定文件的扩展名(必须含有'.')。 ; 注意: 不要在发布的站点上使用这个特性。 error_prepend_string = ;"<font color=#f00>" ; 用于错误信息前输出的字符串 error_append_string = ;"</font>" ; 用于错误信息后输出的字符串 xmlrpc_errors = Off xmlrpc_error_number = 0 ; 尚无文档 [PHP-Core-Logging] define_syslog_variables = Off ; 是否定义各种系统日志变量,如:$LOG_PID, $LOG_CRON 等等。 ; 关掉它以提高效率的好主意。 ; 你可以在运行时调用define_syslog_variables()函数来定义这些变量。 error_log = ; 将错误日志记录到哪个文件中。该文件必须对Web服务器用户可写。 ; syslog 表示记录到系统日志中(NT下的事件日志, Unix下的syslog(3)) ; 如果此处未设置任何值,则错误将被记录到Web服务器的错误日志中。 log_errors = Off ; 是否在日志文件里记录错误,具体在哪里记录取决于error_log指令。 ; 强烈建议你在最终发布的web站点时使用日志记录错误而不是直接输出, ; 这样可以让你既知道那里出了问题,又不会暴露敏感信息。 log_errors_max_len = 1024 ; 设置错误日志中附加的与错误信息相关联的错误源的最大长度。 ; 这里设置的值对显示的和记录的错误以及$php_errormsg都有效。 ; 设为 0 可以允许无限长度。 ignore_repeated_errors = Off ; 记录错误日志时是否忽略重复的错误信息。 ; 错误信息必须出现在同一文件的同一行才被被视为重复。 ignore_repeated_source = Off ; 是否在忽略重复的错误信息时忽略重复的错误源。 [PHP-Core-Mail] ; 要使邮件函数可用,PHP必须在编译时能够访问sendmail程序。 ; 如果使用其它的邮件程序,如qmail或postfix,确保使用了相应的sendmail包装。 ; PHP首先会在系统的PATH环境变量中搜索sendmail,接着按以下顺序搜索: ; /usr/bin:/usr/sbin:/usr/etc:/etc:/usr/ucblib:/usr/lib ; 强烈建议在PATH中能够找到sendmail。 ; 另外,编译PHP的用户必须能够访问sendmail程序。 SMTP = "localhost" ; mail()函数中用来发送邮件的SMTP服务器的主机名称或者IP地址。仅用于win32。 smtp_port = 25 ; SMTP服务器的端口号。仅用于win32。 sendmail_from = ; 发送邮件时使用的"From:"头中的邮件地址。仅用于win32 ; 该选项还同时设置了"Return-Path:"头。 sendmail_path = "-t -i" ; 仅用于unix,也可支持参数(默认的是'sendmail -t -i') ; sendmail程序的路径,通常为"/usr/sbin/sendmail或/usr/lib/sendmail"。 ; configure脚本会尝试找到该程序并设定为默认值,但是如果失败的话,可以在这里设定。 ; 不使用sendmail的系统应将此指令设定为sendmail替代程序(如果有的话)。 ; 例如,Qmail用户通常可以设为"/var/qmail/bin/sendmail"或"/var/qmail/bin/qmail-inject"。 ; qmail-inject 不需要任何选项就能正确处理邮件。 mail.force_extra_parameters = ; 作为额外的参数传递给sendmail库的强制指定的参数附加值。 ; 这些参数总是会替换掉mail()的第5个参数,即使在安全模式下也是如此。 [PHP-Core-ResourceLimit] default_socket_timeout = 60 ; 默认socket超时(秒) max_execution_time = 30 ; 每个脚本最大允许执行时间(秒),0 表示没有限制。 ; 这个参数有助于阻止劣质脚本无休止的占用服务器资源。 ; 该指令仅影响脚本本身的运行时间,任何其它花费在脚本运行之外的时间, ; 如用system()/sleep()函数的使用、数据库查询、文件上传等,都不包括在内。 ; 在安全模式下,你不能用ini_set()在运行时改变这个设置。 memory_limit = 128M ; 一个脚本所能够申请到的最大内存字节数(可以使用K和M作为单位)。 ; 这有助于防止劣质脚本消耗完服务器上的所有内存。 ; 要能够使用该指令必须在编译时使用"--enable-memory-limit"配置选项。 ; 如果要取消内存限制,则必须将其设为 -1 。 ; 设置了该指令后,memory_get_usage()函数将变为可用。 max_input_time = -1 ; 每个脚本解析输入数据(POST, GET, upload)的最大允许时间(秒)。 ; -1 表示不限制。 max_input_nesting_level = 64 ; 输入变量的最大嵌套深度(尚无更多解释文档) post_max_size = 8M ; 允许的POST数据最大字节长度。此设定也影响到文件上传。 ; 如果POST数据超出限制,那么$_POST和$_FILES将会为空。 ; 要上传大文件,该值必须大于upload_max_filesize指令的值。 ; 如果启用了内存限制,那么该值应当小于memory_limit指令的值。 realpath_cache_size = 16K ; 指定PHP使用的realpath(规范化的绝对路径名)缓冲区大小。 ; 在PHP打开大量文件的系统上应当增大该值以提高性能。 realpath_cache_ttl = 120 ; realpath缓冲区中信息的有效期(秒)。 ; 对文件很少变动的系统,可以增大该值以提高性能。 [PHP-Core-FileUpLoad] file_uploads = On ; 是否允许HTTP文件上传。 ; 参见upload_max_filesize, upload_tmp_dir, post_max_size指令 upload_max_filesize = 2M ; 允许上传的文件的最大尺寸。 upload_tmp_dir = ; 文件上传时存放文件的临时目录(必须是PHP进程用户可写的目录)。 ; 如果未指定则PHP使用系统默认的临时目录。 [PHP-Core-MagicQuotes] ; PHP6删除了下列指令,相当于全部为 Off magic_quotes_gpc = Off ; 是否对输入的GET/POST/Cookie数据使用自动字符串转义( ' " \ NULL )。 ; 这里的设置将自动影响 $_GEST $_POST $_COOKIE 数组的值。 ; 若将本指令与magic_quotes_sybase指令同时打开,则仅将单引号(')转义为(''), ; 其它特殊字符将不被转义,即( " \ NULL )将保持原样!! ; 建议关闭此特性,并使用自定义的过滤函数。 magic_quotes_runtime = Off ; 是否对运行时从外部资源产生的数据使用自动字符串转义( ' " \ NULL )。 ; 若打开本指令,则大多数函数从外部资源(数据库,文本文件等)返回数据都将被转义。 ; 例如:用SQL查询得到的数据,用exec()函数得到的数据,等等 ; 若将本指令与magic_quotes_sybase指令同时打开,则仅将单引号(')转义为(''), ; 其它特殊字符将不被转义,即( " \ NULL )将保持原样!! ; 建议关闭此特性,并视具体情况使用自定义的过滤函数。 magic_quotes_sybase = Off ; 是否采用Sybase形式的自动字符串转义(用 '' 表示 ') [PHP-Core-HighLight] highlight.bg = "#FFFFFF" highlight.comment = "#FF8000" highlight.default = "#0000BB" highlight.html = "#000000" highlight.keyword = "#007700" highlight.string = "#DD0000" ; 语法高亮模式的色彩(通常用于显示 .phps 文件)。 ; 只要能被<font color=xxx>接受的东西就能正常工作。 [PHP-Core-Langue] short_open_tag = On ; 是否允许使用"<? ?>"短标识。否则必须使用"<?php ?>"长标识。 ; 除非你的php程序仅在受控环境下运行,且只供自己使用,否则请不要使用短标记。 ; 如果要和XML结合使用PHP,可以选择关闭此选项以方便直接嵌入"<?xml ... ?>", ; 不然你必须用PHP来输出:<? echo '<?xml version="1.0"'; ?> ; 本指令也会影响到缩写形式"<?=",它和"<? echo"等价,要使用它也必须打开短标记。 asp_tags = Off ; 是否允许ASP风格的标记"<% %>",这也会影响到缩写形式"<%="。 ; PHP6中将删除此指令 arg_separator.output = "&" ; PHP所产生的URL中用来分隔参数的分隔符。 ; 另外还可以用"&"或","等等。 arg_separator.input = "&" ; PHP解析URL中的变量时使用的分隔符列表。 ; 字符串中的每一个字符都会被当作分割符。 ; 另外还可以用",&"等等。 allow_call_time_pass_reference = On ; 是否强迫在函数调用时按引用传递参数(每次使用此特性都会收到一条警告)。 ; php反对这种做法,并在PHP6里删除了该指令(相当于设为Off),因为它影响到了代码的整洁。 ; 鼓励的方法是在函数声明里明确指定哪些参数按引用传递。 ; 我们鼓励你关闭这一选项,以保证你的脚本在将来版本的语言里仍能正常工作。 auto_globals_jit = On ; 是否仅在使用到$_SERVER和$_ENV变量时才创建(而不是在脚本一启动时就自动创建)。 ; 如果并未在脚本中使用这两个数组,打开该指令将会获得性能上的提升。 ; 要想该指令生效,必须关闭register_globals和register_long_arrays指令。 auto_prepend_file = auto_append_file = ; 指定在主文件之前/后自动解析的文件名。为空表示禁用该特性。 ; 该文件就像调用了include()函数被包含进来一样,因此会使用include_path指令的值。 ; 注意:如果脚本通过exit()终止,那么自动后缀将不会发生。 variables_order = "EGPCS" ; PHP注册 Environment, GET, POST, Cookie, Server 变量的顺序。 ; 分别用 E, G, P, C, S 表示,按从左到右注册,新值覆盖旧值。 ; 举例说,设为"GP"将会导致用POST变量覆盖同名的GET变量, ; 并完全忽略 Environment, Cookie, Server 变量。 ; 推荐使用"GPC"或"GPCS",并使用getenv()函数访问环境变量。 register_globals = Off ; 是否将 E, G, P, C, S 变量注册为全局变量。 ; 打开该指令可能会导致严重的安全问题,除非你的脚本经过非常仔细的检查。 ; 推荐使用预定义的超全局变量:$_ENV, $_GET, $_POST, $_COOKIE, $_SERVER ; 该指令受variables_order指令的影响。 ; PHP6中已经删除此指令。 register_argc_argv = On ; 是否声明$argv和$argc全局变量(包含用GET方法的信息)。 ; 建议不要使用这两个变量,并关掉该指令以提高性能。 register_long_arrays = On ; 是否启用旧式的长式数组(HTTP_*_VARS)。 ; 鼓励使用短式的预定义超全局数组,并关闭该特性以获得更好的性能。 ; PHP6中已经删除此指令。 always_populate_raw_post_data = Off ; 是否总是生成$HTTP_RAW_POST_DATA变量(原始POST数据)。 ; 否则,此变量仅在遇到不能识别的MIME类型的数据时才产生。 ; 不过,访问原始POST数据的更好方法是 php://input 。 ; $HTTP_RAW_POST_DATA对于enctype="multipart/form-data"的表单数据不可用。 unserialize_callback_func = ; 如果解序列化处理器需要实例化一个未定义的类, ; 这里指定的回调函数将以该未定义类的名字作为参数被unserialize()调用, ; 以免得到不完整的"__PHP_Incomplete_Class"对象。 ; 如果这里没有指定函数,或指定的函数不包含(或实现)那个未定义的类,将会显示警告信息。 ; 所以仅在确实需要实现这样的回调函数时才设置该指令。 ; 若要禁止这个特性,只需置空即可。 y2k_compliance = On ; 是否强制打开2000年适应(可能在非Y2K适应的浏览器中导致问题)。 zend.ze1_compatibility_mode = Off ; 是否使用兼容Zend引擎I(PHP 4.x)的模式。PHP6中将删除该指令(相当于Off)。 ; 这将影响对象的复制、构造(无属性的对象会产生FALSE或0)、比较。 ; 兼容模式下,对象将按值传递,而不是默认的按引用传递。 precision = 14 ; 浮点型数据显示的有效位数。 serialize_precision = 100 ; 将浮点型和双精度型数据序列化存储时的精度(有效位数)。 ; 默认值能够确保浮点型数据被解序列化程序解码时不会丢失数据。 [PHP-Core-OutputControl] ; 输出控制函数很有用,特别是在已经输出了信息之后再发送HTTP头的情况下。 ; 输出控制函数不会作用于header()或setcookie()等函数发送的HTTP头, ; 而只会影响类似于echo()函数输出的信息和嵌入在PHP代码之间的信息。 implicit_flush = Off ; 是否要求PHP输出层在每个输出块之后自动刷新数据。 ; 这等效于在每个 print()、echo()、HTML块 之后自动调用flush()函数。 ; 打开这个选项对程序执行的性能有严重的影响,通常只推荐在调试时使用。 ; 在CLI SAPI的执行模式下,该指令默认为 On 。 output_buffering = 0 ; 输出缓冲区大小(字节)。建议值为4096~8192。 ; 输出缓冲允许你甚至在输出正文内容之后再发送HTTP头(包括cookies)。 ; 其代价是输出层减慢一点点速度。 ; 设置输出缓冲可以减少写入,有时还能减少网络数据包的发送。 ; 这个参数的实际收益很大程度上取决于你使用的是什么Web服务器以及什么样的脚本。 output_handler = ; 将所有脚本的输出重定向到一个输出处理函数。 ; 比如,重定向到mb_output_handler()函数时,字符编码将被透明地转换为指定的编码。 ; 一旦你在这里指定了输出处理程序,输出缓冲将被自动打开(output_buffering=4096)。 ; 注意0: 此处仅能使用PHP内置的函数,自定义函数应在脚本中使用ob_start()指定。 ; 注意1: 可移植脚本不能依赖该指令,而应使用ob_start()函数明确指定输出处理函数。 ; 使用这个指令可能会导致某些你不熟悉的脚本出错。 ; 注意2: 你不能同时使用"mb_output_handler"和"ob_iconv_handler"两个输出处理函数。 ; 你也不能同时使用"ob_gzhandler"输出处理函数和zlib.output_compression指令。 ; 注意3: 如果使用zlib.output_handler指令开启zlib输出压缩,该指令必须为空。 [PHP-Core-Directory] include_path = ".:/path/to/php/pear" ; 指定一组目录用于require(), include(), fopen_with_path()函数寻找文件。 ; 格式和系统的PATH环境变量类似(UNIX下用冒号分隔,Windows下用分号分隔): ; UNIX: "/path1:/path2" ; Windows: "\path1;\path2" ; 在包含路径中使用'.'可以允许相对路径,它代表当前目录。 user_dir = ; 告诉php在使用 /~username 打开脚本时到哪个目录下去找,仅在非空时有效。 ; 也就是在用户目录之下使用PHP文件的基本目录名,例如:"public_html" extension_dir = "/path/to/php" ; 存放扩展库(模块)的目录,也就是PHP用来寻找动态扩展模块的目录。 ; Windows下默认为"C:/php5" [PHP-Core-HTTP] default_mimetype = "text/html" default_charset = ;"gb2312" ; PHP默认会自动输出"Content-Type: text/html" HTTP头。 ; 如果将default_charset指令设为"gb2312", ; 那么将会自动输出"Content-Type: text/html; charset=gb2312"。 ; PHP6反对使用default_charset指令,而推荐使用unicode.output_encoding指令。 [PHP-Core-Unicode] ; PHP6基于ICU(International Components for Unicode)库提供了全面的Unicode支持。 ; 编译时需要使用--with-icu-dir=<dir>指定ICU头文件和库的安装位置。 ; 除detect_unicode外,其他都是PHP6新增的指令。 ; ; PHP6的信息目前还很缺乏,所以此部分内容可能不完整甚至有错误。 detect_unicode = On ; 指示Zend引擎是否通过检查脚本的BOM(字节顺序标记)来检测脚本是否包含多字节字符。 ; 建议关闭。PHP6已经取消了此指令而用unicode.script_encoding指令来代替其功能。 unicode.semantics = Off ; 是否启用Unicode支持。 ; 如果打开此指令,那么PHP将变成一个完全的Unicode环境,比如: ; 所有字符串和从HTTP接受的变量都将变成Unicode,所有PHP标识符也都可以使用Unicode字符。 ; 而且,PHP内部将使用Unicode字符串并负责对外围非Unicode字符进行自动转换, ; 比如:HTTP输入输出、流、文件系统操作等等,甚至连php.ini自身都将按照UTF-8编码来解析。 ; 开启这个指令后,你必须明确指定二进制字符串。PHP将不对二进制字符串的内容做任何假定, ; 因此你的程序必须保证能够恰当的处理二进制字符串。 ; 如果关闭这个指令,PHP的行为将和以前的行为完全相同: ; 字符串不会变成Unicode,文件和二进制字符串也将向后兼容,php.ini也将按照"as-is"风格解析。 ; 不管是否打开此指令,所有的函数和操作符都透明的支持Unicode字符串。 unicode.fallback_encoding = UTF-8 ; 为其他所有unicode.*_encoding指令设置默认值。 ; 也就是说如果某个unicode.*_encoding指令未明确设置的话,将使用此处设置的值。 unicode.runtime_encoding = ; 运行时编码指定了PHP引擎内部转换二进制字符串时使用的编码。 ; 此处的设置对于I/O相关操作(比如:写入标准输出/读取文件系统/解码HTTP输入变量)没有影响。 ; PHP也允许你明确的对字符串进行转换: ; (binary)$str -- 转化为二进制字符串 ; (unicode)$str -- 转化为Unicode字符串 ; (string)$str -- 如果unicode.semantics为On则转化为Unicode字符串,否则转化为二进制 字符串 ; 例如,如果该指令的值为iso-8859-1并且$uni是一个Unicode字符串,那么 ; $str = (binary)$uni ; 将等到一个使用iso-8859-1编码的二进制字符串。 ; 在连接、比较、传递参数等操作之前PHP会将相关字符串隐含转换为Unicode,然后再进行操作。 ; 比如在将二进制字符串与Unicode进行连接的时候, ; PHP将会使用这里的设置将二进制字符串转换为Unicode字符串,然后再进行操作。 unicode.output_encoding = ; PHP输出非二进制字符串使用的编码。 ; 自动将'print'和'echo'之类的输出内容转换为此处设定的编码(并不对二进制字符串进行转换)。 ; 当向文件之类的外部资源写入数据的时候, ; 你必须依赖于流编码特性或者使用Unicode扩展的函数手动的对数据进行编码。 ; 在PHP6中反对使用先前的default_charset指令,而推荐使用该指令。 ; 先前的default_charset指令只是指定了Content-Type头中的字符集,而并不对实际的输出做任何 转换。 ; 而在PHP6中,default_charset指令仅在unicode.semantics为off的时候才有效。 ; 设置了该指令后将在Content-Type输出头的'charset'部分填上该指令的值, ; 而不管default_charset指令如何设置。 unicode.http_input_encoding = ; 通过HTTP获取的变量(比如$_GET和_$POST)内容的编码。 ; 直到2007年4月此功能尚在开发中.... unicode.filesystem_encoding = ; 文件系统的目录名和文件名的编码。 ; 文件系统相关的函数(比如opendir())将使用这个编码接受和返回文件名和目录名。 ; 此处的设置必须与文件系统实际使用的编码完全一致。 unicode.script_encoding = ; PHP脚本自身的默认编码。 ; 你可以使用任何ICU支持的编码来写PHP脚本。 ; 如果你想针对单独的脚本文件设定其编码,可以在该脚本的开头使用 ; <?php declare(encoding = 'Shift-JIS'); ?> ; 来指定。注意:必须是第一行开头,全面不要有任何字符(包括空白)。 ; 该方法只能影响其所在的脚本,不会影响任何被包含的其他脚本。 unicode.stream_encoding = UTF-8 unicode.from_error_mode = 2 unicode.from_error_subst_char = 3f ; 尚无文档 [PHP-Core-Misc] auto_detect_line_endings = Off ; 是否让PHP自动侦测行结束符(EOL)。 ; 如果的你脚本必须处理Macintosh文件, ; 或者你运行在Macintosh上,同时又要处理unix或win32文件, ; 打开这个指令可以让PHP自动侦测EOL,以便fgets()和file()函数可以正常工作。 ; 但同时也会导致在Unix系统下使用回车符(CR)作为项目分隔符的人遭遇不兼容行为。 ; 另外,在检测第一行的EOL习惯时会有很小的性能损失。 browscap = ;"c:/windows/system32/inetsrv/browscap.ini" ; 只有PWS和IIS需要这个设置 ; 你可以从http://www.garykeith.com/browsers/downloads.asp ; 得到一个browscap.ini文件。 ignore_user_abort = Off ; 是否即使在用户中止请求后也坚持完成整个请求。 ; 在执行一个长请求的时候应当考虑打开该它, ; 因为长请求可能会导致用户中途中止或浏览器超时。 user_agent = ;"PHP" ; 定义"User-Agent"字符串 ;url_rewriter.tags = "a=href,area=href,frame=src,form=,fieldset=" ; 虽然此指令属于PHP核心部分,但是却用于Session模块的配置 ;extension = ; 在PHP启动时加载动态扩展。例如:extension=mysqli.so ; "="之后只能使用模块文件的名字,而不能含有路径信息。 ; 路径信息应当只由extension_dir指令提供。 ; 主意,在windows上,下列扩展已经内置: ; bcmath ; calendar ; com_dotnet ; ctype ; session ; filter ; ftp ; hash ; iconv ; json ; odbc ; pcre ; Reflection ; date ; libxml ; standard ; tokenizer ; zlib ; SimpleXML ; dom ; SPL ; wddx ; xml ; xmlreader ; xmlwriter [PHP-Core-CGI] ; 这些指令只有在将PHP运行在CGI模式下的时候才有效 doc_root = ; PHP的"CGI根目录"。仅在非空时有效。 ; 在web服务器的主文档目录(比如"htdocs")中放置可执行程序/脚本被认为是不安全的, ; 比如因为配置错误而将脚本作为普通的html显示。 ; 因此很多系统管理员都会在主文档目录之外专门设置一个只能通过CGI来访问的目录, ; 该目录中的内容只会被解析而不会原样显示出来。 ; 如果设置了该项,那么PHP就只会解释doc_root目录下的文件, ; 并确保目录外的脚本都不会被PHP解释器执行(user_dir除外)。 ; 如果编译PHP时没有指定FORCE_REDIRECT,并且在非IIS服务器上以CGI方式运行, ; 则必须设置此指令(参见手册中的安全部分)。 ; 替代方案是使用的cgi.force_redirect指令。 cgi.discard_path = Off ; 尚无文档(PHP6新增指令) cgi.fix_pathinfo = On ; 是否为CGI提供真正的 PATH_INFO/PATH_TRANSLATED 支持(遵守cgi规范)。 ; 先前的行为是将PATH_TRANSLATED设为SCRIPT_FILENAME,而不管PATH_INFO是什么。 ; 打开此选项将使PHP修正其路径以遵守CGI规范,否则仍将使用旧式的不合规范的行为。 ; 鼓励你打开此指令,并修正脚本以使用 SCRIPT_FILENAME 代替 PATH_TRANSLATED 。 ; 有关PATH_INFO的更多信息请参见cgi规范。 cgi.force_redirect = On ; 是否打开cgi强制重定向。强烈建议打开它以为CGI方式运行的php提供安全保护。 ; 你若自己关闭了它,请自己负责后果。 ; 注意:在IIS/OmniHTTPD/Xitami上则必须关闭它! cgi.redirect_status_env = ; 如果cgi.force_redirect=On,并且在Apache与Netscape之外的服务器下运行PHP, ; 可能需要设定一个cgi重定向环境变量名,PHP将去寻找它来知道是否可以继续执行下去。 ; 设置这个变量会导致安全漏洞,请务必在设置前搞清楚自己在做什么。 cgi.rfc2616_headers = 0 ; 指定PHP在发送HTTP响应代码时使用何种报头。 ; 0 表示发送一个"Status: "报头,Apache和其它web服务器都支持。 ; 若设为1,则PHP使用RFC2616标准的头。 ; 除非你知道自己在做什么,否则保持其默认值 0 cgi.nph = Off ; 在CGI模式下是否强制对所有请求都发送"Status: 200"状态码。 cgi.check_shebang_line =On ; CGI PHP是否检查脚本顶部以 #! 开始的行。 ; 如果脚本想要既能够单独运行又能够在PHP CGI模式下运行,那么这个起始行就是必须的。 ; 如果打开该指令,那么CGI模式的PHP将跳过这一行。 fastcgi.impersonate = Off ; IIS中的FastCGI支持模仿客户端安全令牌的能力。 ; 这使得IIS能够定义运行时所基于的请求的安全上下文。 ; Apache中的mod_fastcgi不支持此特性(03/17/2002) ; 如果在IIS中运行则设为On,默认为Off。 fastcgi.logging = On ; 是否记录通过FastCGI进行的连接。 [PHP-Core-Weirdy] ; 这些选项仅存在于文档中,却不存在于phpinfo()函数的输出中 async_send = Off ; 是否异步发送。 from = ;"john@doe.com" ; 定义匿名ftp的密码(一个email地址) ;;;;;;;;;;;;;;;;;; ;; 近核心模块 ;; ;;;;;;;;;;;;;;;;;; [Pcre] ;Perl兼容正则表达式模块 pcre.backtrack_limit = 100000 ; PCRE的最大回溯(backtracking)步数。 pcre.recursion_limit = 100000 ; PCRE的最大递归(recursion)深度。 ; 如果你将该值设的非常高,将可能耗尽进程的栈空间,导致PHP崩溃。 [Session] ; 除非使用session_register()或$_SESSION注册了一个变量。 ; 否则不管是否使用了session_start(),都不会自动添加任何session记录。 ; 包括resource变量或有循环引用的对象包含指向自身的引用的对象,不能保存在会话中。 ; register_globals指令会影响到会话变量的存储和恢复。 session.save_handler = "files" ; 存储和检索与会话关联的数据的处理器名字。默认为文件("files")。 ; 如果想要使用自定义的处理器(如基于数据库的处理器),可用"user"。 ; 设为"memcache"则可以使用memcache作为会话处理器(需要指定"--enable-memcache-session"编译选项)。 ; 还有一个使用PostgreSQL的处理器:http://sourceforge.net/projects/phpform-ext/ session.save_path = "/tmp" ; 传递给存储处理器的参数。对于files处理器,此值是创建会话数据文件的路径。 ; Windows下默认为临时文件夹路径。 ; 你可以使用"N;[MODE;]/path"这样模式定义该路径(N是一个整数)。 ; N表示使用N层深度的子目录,而不是将所有数据文件都保存在一个目录下。 ; [MODE;]可选,必须使用8进制数,默认"600",表示文件的访问权限。 ; 这是一个提高大量会话性能的好主意。 ; 注意0: "N;[MODE;]/path"两边的双引号不能省略。 ; 注意1: [MODE;]并不会改写进程的umask。 ; 注意2: php不会自动创建这些文件夹结构。请使用ext/session目录下的mod_files.sh脚本创建。 ; 注意3: 如果该文件夹可以被不安全的用户访问(比如默认的"/tmp"),那么将会带来安全漏洞。 ; 注意4: 当N>0时自动垃圾回收将会失效,具体参见下面有关垃圾搜集的部分。 ; [安全提示]建议针对每个不同的虚拟主机分别设置各自不同的目录。 ; ; 对于"memcache"处理器,需要定义一个逗号分隔的服务器URL用来存储会话数据。 ; 比如:"tcp://host1:11211, tcp://host2:11211" ; 每个URL都可以包含传递给那个服务器的参数,可用的参数与 Memcache::addServer() 方法相同。 ; 例如:"tcp://host1:11211?persistent=1&weight=1&timeout=1&retry_interval=15" session.name = "PHPSESSID" ;用在cookie里的会话ID标识名,只能包含字母和数字。 session.auto_start = Off ; 在客户访问任何页面时都自动初始化会话,默认禁止。 ; 因为类定义必须在会话启动之前被载入,所以若打开这个选项,你就不能在会话中存放对象。 session.serialize_handler = "php" ; 用来序列化/解序列化数据的处理器,php是标准序列化/解序列化处理器。 ; 另外还可以使用"php_binary"。当启用了WDDX支持以后,将只能使用"wddx"。 session.gc_probability = 1 session.gc_divisor = 100 ; 定义在每次初始化会话时,启动垃圾回收程序的概率。 ; 这个收集概率计算公式如下:session.gc_probability/session.gc_divisor ; 对会话页面访问越频繁,概率就应当越小。建议值为1/1000~5000。 session.gc_maxlifetime = 1440 ; 超过此参数所指的秒数后,保存的数据将被视为'垃圾'并由垃圾回收程序清理。 ; 判断标准是最后访问数据的时间(对于FAT文件系统是最后刷新数据的时间)。 ; 如果多个脚本共享同一个session.save_path目录但session.gc_maxlifetime不同, ; 那么将以所有session.gc_maxlifetime指令中的最小值为准。 ; 如果使用多层子目录来存储数据文件,垃圾回收程序不会自动启动。 ; 你必须使用一个你自己编写的shell脚本、cron项或者其他办法来执行垃圾搜集。 ; 比如,下面的脚本相当于设置了"session.gc_maxlifetime=1440" (24分钟): ; cd /path/to/sessions; find -cmin +24 | xargs rm session.referer_check = ; 如果请求头中的"Referer"字段不包含此处指定的字符串则会话ID将被视为无效。 ; 注意:如果请求头中根本不存在"Referer"字段的话,会话ID将仍将被视为有效。 ; 默认为空,即不做检查(全部视为有效)。 session.entropy_file = ;"/dev/urandom" ; 附加的用于创建会话ID的外部高熵值资源(文件), ; 例如UNIX系统上的"/dev/random"或"/dev/urandom" session.entropy_length = 0 ; 从高熵值资源中读取的字节数(建议值:16)。 session.use_cookies = On ; 是否使用cookie在客户端保存会话ID session.use_only_cookies = Off ; 是否仅仅使用cookie在客户端保存会话ID。PHP6的默认值为On。 ; 打开这个选项可以避免使用URL传递会话带来的安全问题。 ; 但是禁用Cookie的客户端将使会话无法工作。 session.cookie_lifetime = 0 ; 传递会话ID的Cookie有效期(秒),0 表示仅在浏览器打开期间有效。 ; [提示]如果你不能保证服务器时间和客户端时间严格一致请不要改变此默认值! session.cookie_path = "/" ; 传递会话ID的Cookie作用路径。 session.cookie_domain = ; 传递会话ID的Cookie作用域。 ; 默认为空表示表示根据cookie规范生成的主机名。 session.cookie_secure = Off ; 是否仅仅通过安全连接(https)发送cookie。 session.cookie_httponly = Off ; 是否在cookie中添加httpOnly标志(仅允许HTTP协议访问), ; 这将导致客户端脚本(JavaScript等)无法访问该cookie。 ; 打开该指令可以有效预防通过XSS攻击劫持会话ID。 session.cache_limiter = "nocache" ; 设为{nocache|private|public}以指定会话页面的缓存控制模式, ; 或者设为空以阻止在http应答头中发送禁用缓存的命令。 session.cache_expire = 180 ; 指定会话页面在客户端cache中的有效期限(分钟) ; session.cache_limiter=nocache时,此处设置无效。 session.use_trans_sid = Off ; 是否使用明码在URL中显示SID(会话ID)。 ; 默认是禁止的,因为它会给你的用户带来安全危险: ; 1- 用户可能将包含有效sid的URL通过email/irc/QQ/MSN...途径告诉给其他人。 ; 2- 包含有效sid的URL可能会被保存在公用电脑上。 ; 3- 用户可能保存带有固定不变sid的URL在他们的收藏夹或者浏览历史纪录里面。 ; 基于URL的会话管理总是比基于Cookie的会话管理有更多的风险,所以应当禁用。 session.bug_compat_42 = On session.bug_compat_warn = On ; PHP4.2之前的版本有一个未注明的"BUG": ; 即使在register_globals=Off的情况下也允许初始化全局session变量, ; 如果你在PHP4.3之后的版本中使用这个特性,会显示一条警告。 ; 建议关闭该"BUG"并显示警告。PHP6删除了这两个指令,相当于全部设为Off。 session.hash_function = 0 ; 生成SID的散列算法。SHA-1的安全性更高一些 ; 0: MD5 (128 bits) ; 1: SHA-1 (160 bits) ; 建议使用SHA-1。 session.hash_bits_per_character = 4 ; 指定在SID字符串中的每个字符内保存多少bit, ; 这些二进制数是hash函数的运算结果。 ; 4: 0-9, a-f ; 5: 0-9, a-v ; 6: 0-9, a-z, A-Z, "-", "," ; 建议值为 5 url_rewriter.tags = "a=href,area=href,frame=src,form=,fieldset=" ; 此指令属于PHP核心部分,并不属于Session模块。 ; 指定重写哪些HTML标签来包含SID(仅当session.use_trans_sid=On时有效) ; form和fieldset比较特殊: ; 如果你包含他们,URL重写器将添加一个隐藏的"<input>",它包含了本应当额外追加到URL上的信息。 ; 如果要符合XHTML标准,请去掉form项并在表单字段前后加上<fieldset>标记。 ; 注意:所有合法的项都需要一个等号(即使后面没有值)。 ; 推荐值为"a=href,area=href,frame=src,input=src,form=fakeentry"。 session.encode_sources = "globals" ; PHP6中有争议的指令,尚未决定是否采用该指令。也尚无相关文档。 ;;;;;;;;;;;;;;;; ;; 其他模块 ;; ;;;;;;;;;;;;;;;; [APC-3.0.16] ; Alternative PHP Cache 用于缓存和优化PHP中间代码 ; 编译/安装/配置信息都位于源码树下的 INSTALL 文件中 apc.enabled = On ; 是否启用APC,如果APC被静态编译进PHP又想禁用它,这是唯一的办法。 apc.enable_cli = Off ; 是否为CLI版本启用APC功能,仅用于测试和调试目的才打开此指令。 apc.cache_by_default = On ; 是否默认对所有文件启用缓冲。 ; 若设为Off并与以加号开头的apc.filters指令一起用,则文件仅在匹配过滤器时才被缓存。 apc.file_update_protection = 2 ; 当你在一个运行中的服务器上修改文件时,你应当执行原子操作。 ; 也就是先写进一个临时文件,然后将该文件重命名(mv)到最终的名字。 ; 文本编辑器以及 cp, tar 等程序却并不是这样操作的,从而导致有可能缓冲了残缺的文件。 ; 默认值 2 表示在访问文件时如果发现修改时间距离访问时间小于 2 秒则不做缓冲。 ; 那个不幸的访问者可能得到残缺的内容,但是这种坏影响却不会通过缓存扩大化。 ; 如果你能确保所有的更新操作都是原子操作,那么可以用 0 关闭此特性。 ; 如果你的系统由于大量的IO操作导致更新缓慢,你就需要增大此值。 apc.filters = ; 一个以逗号分隔的POSIX扩展正则表达式列表。 ; 如果源文件名与任意一个模式匹配,则该文件不被缓存。 ; 注意,用来匹配的文件名是传递给include/require的文件名,而不是绝对路径。 ; 如果正则表达式的第一个字符是"+"则意味着任何匹配表达式的文件会被缓存, ; 如果第一个字符是"-"则任何匹配项都不会被缓存。"-"是默认值,可以省略掉。 apc.ttl = 0 ; 缓存条目在缓冲区中允许逗留的秒数。0 表示永不超时。建议值为7200~86400。 ; 设为 0 意味着缓冲区有可能被旧的缓存条目填满,从而导致无法缓存新条目。 apc.user_ttl = 0 ; 类似于apc.ttl,只是针对每个用户而言,建议值为7200~86400。 ; 设为 0 意味着缓冲区有可能被旧的缓存条目填满,从而导致无法缓存新条目。 apc.gc_ttl = 3600 ; 缓存条目在垃圾回收表中能够存在的秒数。 ; 此值提供了一个安全措施,即使一个服务器进程在执行缓存的源文件时崩溃, ; 而且该源文件已经被修改,为旧版本分配的内存也不会被回收,直到达到此TTL值为止。 ; 设为零将禁用此特性。 apc.include_once_override = Off ; 优化include_once()和require_once()函数以避免执行额外的系统调用。 apc.max_file_size = 1M ; 禁止大于此尺寸的文件被缓存。 apc.mmap_file_mask = ; 如果使用--enable-mmap(默认启用)为APC编译了MMAP支持, ; 这里的值就是传递给mmap模块的mktemp风格的文件掩码(建议值为"/tmp/apc.XXXXXX")。 ; 该掩码用于决定内存映射区域是否要被file-backed或者shared memory backed。 ; 对于直接的file-backed内存映射,要设置成"/tmp/apc.XXXXXX"的样子(恰好6个X)。 ; 要使用POSIX风格的shm_open/mmap就需要设置成"/apc.shm.XXXXXX"的样子。 ; 你还可以设为"/dev/zero"来为匿名映射的内存使用内核的"/dev/zero"接口。 ; 不定义此指令则表示强制使用匿名映射。 apc.num_files_hint = 1000 ; Web服务器上可能被包含或被请求的不同脚本源代码文件的大致数量(建议值为1024~4096)。 ; 如果你不能确定,则设为 0 ;此设定主要用于拥有数千个源文件的站点。 apc.optimization = 0 ; 优化级别(建议值为 0 ) 。反对使用该指令。将来可能会被删除。 ; 正整数值表示启用优化器,值越高则使用越激进的优化。 ; 更高的值可能有非常有限的速度提升,但目前尚在试验中。 apc.report_autofilter = Off ; 是否记录所有由于early/late binding原因而自动未被缓存的脚本。 apc.shm_segments = 1 ; 为编译器缓冲区分配的共享内存块数量(建议值为1)。 ; 如果APC耗尽了共享内存,并且已将apc.shm_size指令设为系统允许的最大值,可以尝试增大此值。 ; 在mmap模式下设置为 1 之外的其它值是无效的,因为经过mmap的共享内存段的大小是没有限制的。 apc.shm_size = 30 ; 每个共享内存块的大小(以MB为单位,建议值为128~256)。 ; 有些系统(包括大多数BSD变种)默认的共享内存块尺寸很小。 apc.slam_defense = 0 ; 在非常繁忙的服务器上,无论是启动服务还是修改文件, ; 都可能由于多个进程企图同时缓存一个文件而导致竞争条件。 ; 这个指令用于设置进程在处理未被缓存的文件时跳过缓存步骤的百分率。 ; 比如设为75表示在遇到未被缓存的文件时有75%的概率不进行缓存,从而减少碰撞几率。 ; 反对使用该指令,鼓励设为 0 来禁用这个特性。建议该用apc.write_lock指令。 apc.stat = On ; 是否启用脚本更新检查。 ; 改变这个指令值要非常小心。 ; 默认值 On 表示APC在每次请求脚本时都检查脚本是否被更新, ; 如果被更新则自动重新编译和缓存编译后的内容。但这样做对性能有不利影响。 ; 如果设为 Off 则表示不进行检查,从而使性能得到大幅提高。 ; 但是为了使更新的内容生效,你必须重启Web服务器。 ; 这个指令对于include/require的文件同样有效。但是需要注意的是, ; 如果你使用的是相对路径,APC就必须在每一次include/require时都进行检查以定位文件。 ; 而使用绝对路径则可以跳过检查,所以鼓励你使用绝对路径进行include/require操作。 apc.user_entries_hint = 4096 ; 类似于num_files_hint指令,只是针对每个不同用户而言。 ; 如果你不能确定,则设为 0 。 apc.write_lock = On ; 是否启用写入锁。 ; 在非常繁忙的服务器上,无论是启动服务还是修改文件, ; 都可能由于多个进程企图同时缓存一个文件而导致竞争条件。 ; 启用该指令可以避免竞争条件的出现。 apc.rfc1867 = Off ; 打开该指令后,对于每个恰好在file字段之前含有APC_UPLOAD_PROGRESS字段的上传文件, ; APC都将自动创建一个upload_<key>的用户缓存条目(<key>就是APC_UPLOAD_PROGRESS字段值)。 ; 需要注意的是,文件上传跟踪在这里并不是线程安全的, ; 所以如果老文件尚未上载完毕且新文件已经开始上载,那么将丢失对老文件的跟踪。 apc.rfc1867_prefix = "upload_" ; 用于rfc1867上传文件的缓冲项条目名称前缀 apc.rfc1867_name = "APC_UPLOAD_PROGRESS" ; 需要由APC处理的上传文件的rfc1867隐含表单项名称 apc.rfc1867_freq = 0 ; 用户rfc1867上传文件缓存项的更新频率。 ; 取值可以是总文件大小的百分比,或者以'K','M','G'结尾的绝对尺寸。 ; 0 表示尽可能快的更新,不过这样可能会导致运行速度下降。 apc.localcache = Off ; 是否使用非锁定本地进程shadow-cache ,它可以减少了向缓冲区写入时锁之间的竞争。 apc.localcache.size = 512 ; 本地进程的shadow-cache,应当设为一个足够大的值,大约相当于num_files_hint的一半。 apc.stat_ctime = Off ; 尚无文档 [bcmath] ; 为任意精度数学计算提供了二进制计算器(Binary Calculator), ; 它支持任意大小和精度的数字,以字符串形式描述。 bcmath.scale = 0 ; 用于所有bcmath函数的10十进制数的个数 [GD] gd.jpeg_ignore_warning = Off ; 是否忽略jpeg解码器的警告信息(比如无法识别图片格式)。 ; 有image/jpeg与image/pjpeg两种MIME类型,GD库只能识别前一种传统格式。 ; 参见:http://twpug.net/modules/newbb/viewtopic.php?topic_id=1867&forum=14 ; http://bugs.php.net/bug.php?id=29878 ; http://www.faqs.org/faqs/jpeg-faq/part1/section-11.html [Filter] ; 对来源不可靠的数据进行确认和过滤,本扩展模块是实验性的。 filter.default = "unsafe_raw" ; 使用指定的过滤器过滤$_GET,$_POST,$_COOKIE,$_REQUEST数据, ; 原始数据可以通过input_get()函数访问。 ; "unsafe_raw"表示不做任何过滤。 filter.default_flags = ; filter_data()函数的默认标志。 [mbstring] ;多字节字符串模块支持 mbstring.language = "neutral" ; 默认的NLS(本地语言设置),可设置值如下: ; 默认值"neutral"表示中立,相当于未知。 ; "zh-cn"或"Simplified Chinese"表示简体中文 ; "zh-tw"或"Traditional Chinese"表示繁体中文 ; "uni"或"universal"表示Unicode ; 该指令自动定义了随后的mbstring.internal_encoding指令默认值, ; 并且mbstring.internal_encoding指令必须放置在该指令之后。 mbstring.internal_encoding = ; 本指令必须放置在mbstring.language指令之后。 ; 默认的内部编码,未设置时取决于mbstring.language指令的值: ; "neutral" 对应 "ISO-8859-1" ; "zh-cn" 对应 "EUC-CN" (等价于"GB2312") ; "zh-tw" 对应 "EUC-TW" (等价于"BIG5") ; "uni" 对应 "UTF-8" ; 提醒:对于简体中文还可以强制设置为"CP936" (等价于"GBK") ; 注意:可能 SJIS, BIG5, GBK 不适合作为内部编码,不过"GB2312"肯定没问题。 ; 建议手动强制指定 mbstring.encoding_translation = Off ; 是否对进入的HTTP请求按照mbstring.internal_encoding指令进行透明的编码转换, ; 也就是自动检测输入字符的编码并将其透明的转化为内部编码。 ; 可移植的库或者程序千万不要依赖于自动编码转换。 mbstring.http_input = "pass" ; 默认的HTTP输入编码,"pass"表示跳过(不做转换) ; "aotu"的含义与mbstring.detect_order指令中的解释一样。 ; 可以设置为一个单独的值,也可以设置为一个逗号分隔的列表。 mbstring.http_output = "pass" ; 默认的HTTP输出编码,"pass"表示跳过(不做转换) ; "aotu"的含义与mbstring.detect_order指令中的解释一样。 ; 可以设置为一个单独的值,也可以设置为一个逗号分隔的列表。 ; 必须将output_handler指令设置为"mb_output_handler"才可以。 mbstring.detect_order = ; 默认的编码检测顺序,"pass"表示跳过(不做转换)。 ; 默认值("auto")随mbstring.language指令的不同而变化: ; "neutral"和"universal" 对应 "ASCII, UTF-8" ; "Simplified Chinese" 对应 "ASCII, UTF-8, EUC-CN, CP936" ; "Traditional Chinese" 对应 "ASCII, UTF-8, EUC-TW, BIG-5" ; 建议在可控环境下手动强制指定一个单一值 mbstring.func_overload = 0 ; 自动使用 mb_* 函数重载相应的单字节字符串函数。 ; 比如:mail(), ereg() 将被自动替换为mb_send_mail(), mb_ereg() ; 可用 0,1,2,4 进行位组合。比如7表示替换所有。具体替换说明如下: ; 0: 无替换 ; 1: mail() → mb_send_mail() ; 2: strlen() → mb_strlen() ; substr() → mb_substr() ; strpos() → mb_strpos() ; strrpos() → mb_strrpos() ; strtolower() → mb_strtolower() ; strtoupper() → mb_strtoupper() ; substr_count() → mb_substr_count() ; 4: ereg() → mb_ereg() ; eregi() → mb_eregi() ; ereg_replace() → mb_ereg_replace() ; eregi_replace() → mb_eregi_replace() ; split() → mb_split() mbstring.script_encoding = ; 脚本所使用的编码 mbstring.strict_detection = Off ; 是否使用严谨的编码检测 mbstring.substitute_character = ; 当某个字符无法解码时,就是用这个字符替代。 ; 若设为一个整数则表示对应的Unicode值,不设置任何值表示不显示这个错误字符。 ; 建议设为"□" [Mcrypt] ; 一个mcrypt库的接口,该库支持许多种块加密算法。 ; 不建议使用该模块,因为毛病太多,建议在数据库层进行加密。 mcrypt.algorithms_dir = ; 默认的加密算法模块所在目录。通常是"/usr/local/lib/libmcrypt"。 ; 目前尚无详细说明文档,此处的解释可能是错误的。 mcrypt.modes_dir = ; 默认的加密模式模块所在目录。通常是"/usr/local/lib/libmcrypt"。 ; 目前尚无说明文档,此处的解释可能是错误的。 [Memcache-2.2.2] ; 一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表, ; 它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。 memcache.allow_failover = On ; 是否在遇到错误时透明地向其他服务器进行故障转移。 memcache.chunk_size = 8192 ; 数据将按照此值设定的块大小进行转移。此值越小所需的额外网络传输越多。 ; 如果发现无法解释的速度降低,可以尝试将此值增加到32768。 memcache.default_port = 11211 ; 连接到memcached服务器时使用的默认TCP端口。 memcache.max_failover_attempts = 20 ; 接受和发送数据时最多尝试多少个服务器,进在打开memcache.allow_failover时有效。 memcache.hash_strategy = "standard" ; 控制将key映射到server的策略。默认值"standard"表示使用先前版本的老hash策略。 ; 设为"consistent"可以允许在连接池中添加/删除服务器时不必重新计算key与server之间的映射关 系。 memcache.hash_function = "crc32" ; 控制将key映射到server的散列函数。默认值"crc32"使用CRC32算法,而"fnv"则表示使用FNV-1a 算法。 ; FNV-1a比CRC32速度稍低,但是散列效果更好。 [Zlib] ; 该模块允许PHP透明的读取和写入gzip(.gz)压缩文件。 zlib.output_compression = Off ; 是否使用zlib库透明地压缩脚本输出结果。 ; 该指令的值可以设置为:Off、On、字节数(压缩缓冲区大小,默认为4096)。 ; 如果打开该指令,当浏览器发送"Accept-Encoding: gzip(deflate)"头时, ; "Content-Encoding: gzip(deflate)"和"Vary: Accept-Encoding"头将加入到应答头当中。 ; 你可以在应答头输出之前用ini_set()函数在脚本中启用或禁止这个特性。 ; 如果输出一个"Content-Type: image/??"这样的应答头,压缩将不会启用(为了防止Netscape的 bug)。 ; 你可以在输出"Content-Type: image/??"之后使用"ini_set('zlib.output_compression', 'On')"重新打开这个特性。 ; 注意1: 压缩率会受压缩缓冲区大小的影响,如果你想得到更好的压缩质量,请指定一个较大的压缩缓冲 区。 ; 注意2: 如果启用了zlib输出压缩,output_handler指令必须为空,同时必须设置 zlib.output_handler指令的值。 zlib.output_compression_level = -1 ; 压缩级别,可用值为 0~9 ,0表示不压缩。值越高效果越好,但CPU占用越多,建议值为1~5。 ; 默认值 -1 表示使用zlib内部的默认值(6)。 zlib.output_handler = ; 在打开zlib.output_compression指令的情况下,你只能在这里指定输出处理器。 ; 可以使用的处理器有"zlib.inflate"(解压)或"zlib.deflate"(压缩)。 ; 如果启用该指令则必须将output_handler指令设为空。 [dbx] ; 一个数据库抽象层,为不同数据库提供了统一的接口。目前支持: ; FrontBase,SQL Server,MySQL,ODBC,PostgreSQL,Sybase-CT,Oracle 8,SQLite dbx.colnames_case = "unchanged" ; 字段名可以按照"unchanged"或"uppercase","lowercase"方式返回。 [MySQLi] ; MySQLi模块只能与4.1.3以上版本的MySQL一起工作。 mysqli.max_links = -1 ; 每个进程中允许的最大连接数(持久和非持久)。-1 代表无限制 mysqli.default_port = 3306 ; mysqli_connect()连接到MySQL数据库时使用的默认TCP端口。 ; 如果没有在这里指定默认值,将按如下顺序寻找: ; (1)$MYSQL_TCP_PORT环境变量 ; (2)/etc/services文件中的mysql-tcp项(unix) ; (3)编译时指定的MYSQL_PORT常量 ; 注意:Win32下,只使用MYSQL_PORT常量。 mysqli.default_socket = ; mysqli_connect()连接到本机MySQL服务器时所使用的默认套接字名。 ; 若未指定则使用内置的MqSQL默认值。 mysqli.default_host = ; mysqli_connect()连接到MySQL数据库时使用的默认主机。安全模式下无效。 mysqli.default_user = ; mysqli_connect()连接到MySQL数据库时使用的默认用户名。安全模式下无效。 mysqli.default_pw = ; mysqli_connect()连接到MySQL数据库时使用的默认密码。安全模式下无效。 ; 在配置文件中保存密码是个坏主意,任何使用PHP权限的用户都可以运行 ; 'echo cfg_get_var("mysql.default_password")'来显示密码! ; 而且任何对该配置文件有读权限的用户也能看到密码。 mysqli.reconnect = Off ; 是否允许重新连接 [PostgresSQL] ;PostgresSQL模块建议与8.0以上版本一起工作。 pgsql.allow_persistent = On ; 是否允许持久连接 pgsql.max_persistent = -1 ; 每个进程中允许的最大持久连接数。-1 代表无限制。 pgsql.max_links = -1 ; 每个进程中允许的最大连接数(持久和非持久)。-1 代表无限制。 pgsql.auto_reset_persistent = Off ; 自动复位在pg_pconnect()上中断了的持久连接,检测需要一些额外开销。 pgsql.ignore_notice = Off ; 是否忽略PostgreSQL后端的提醒消息。 ; 记录后端的提醒消息需要一些很小的额外开销。 pgsql.log_notice = Off ; 是否在日志中记录PostgreSQL后端的提醒消息。 ; 仅在pgsql.ignore_notice=Off时,才可以记录。
生成自定义随机字符串的方法
看代码:
function generate_custom_key($length = 8 ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|'; $charslen = strlen($chars); $str = ''; for($i = 0; $i < $length; $i++ ) { $str .= $chars[mt_rand(0, $charslen - 1)]; } return $str; }
绝对随机,绝对难破解,有没有发现?
attrs.xml文件中format属性值的格式
基本类型:
"reference" //引用 "color" //颜色 "boolean" //布尔值 "dimension" //尺寸值 "float" //浮点值 "integer" //整型值 "string" //字符串 "fraction" //百分数,比如200%
枚举类型:
<attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr>
使用方法:
android:orientation = "vertical"
标志位、位或运算,格式如下:
<attr name="windowSoftInputMode"> <flag name = "stateUnspecified" value = "0" /> <flag name = "stateUnchanged" value = "1" /> <flag name = "stateHidden" value = "2" /> <flag name = "stateAlwaysHidden" value = "3" /> <flag name = "stateVisible" value = "4" /> <flag name = "stateAlwaysVisible" value = "5" /> <flag name = "adjustUnspecified" value = "0x00" /> <flag name = "adjustResize" value = "0x10" /> <flag name = "adjustPan" value = "0x20" /> <flag name = "adjustNothing" value = "0x30" /> </attr>
使用方法:
android:windowSoftInputMode = "stateUnspecified|stateUnchanged|stateHidden"
属性定义可以指定多种类型:
<attr name="background" format="reference|color" />
使用方法:
android:background = "@drawable/图片ID|#00FF00"
代码之美 - 如何写出优雅的PHP代码
写出优秀的程序代码是一门艺术,要想如此,就必须在一开始就养成良好的编程习惯。良好的编程习惯不仅有助于项目初期的设计(如模块化),还可以使你编写的代码更易于理解,从而使代码的维护工作更轻松、更省力。不好的编程习惯则会造成代码bug,并且会使以后的维护工作困难重重。
本文以PHP语言为例,介绍一些良好的编程习惯,希望能够对你有所帮助。
1. 规划代码结构
优秀的PHP代码应该有清晰的结构。PHP面向对象的特性允许程序员将应用程序分解为函数或方法。如果代码晦涩难懂,你也可以添加注释,使代码的功能一目了然。编码时应尽量将前端代码(HTML/CSS/JavaScript)与应用程序的服务端规则分开,或者你可以使用遵循MVC模式的PHP框架来构建你的应用程序。
2. 编码风格统一
优秀的PHP代码应该具备统一的风格。比如,为变量和函数制定统一的命名规则,为循环任务(比如数据库存取、错误处理)制定统一的接入标准,或者保持有规律的代码缩进,这些编码习惯都可以让别人阅读代码更加轻松。
3. 可移植性
优秀的PHP代码应该具有可移植性。程序员应学会运用PHP现有的特性(比如魔术引号和短标签等),应该了解产品需求,适应PHP的特点,保证写出的PHP代码具有可移植性和跨平台性。
4. 代码安全性
优秀的PHP代码应该具有安全性。PHP5具有卓越的特性和灵活性,但应用程序的安全往往掌握在程序员的手中。作为专业的PHP开发人员,应该对安全漏洞有一些深入了解,常见的安全漏洞有跨站脚本攻击(XSS)、跨站请求伪造(CSRF)、代码注入漏洞和字符编码漏洞等。使用PHP中的特定功能和函数(比如mysql_real_escape_string等)可以帮助程序员写出安全的代码。
5. 添加注释
代码注释是代码中的重要组成部分,它解释了函数运行的目的,这种注释会在代码以后的维护中提供非常有用的帮助。
6. 避免简写标记
应使用完整的起始标记,不推荐使用简写的起始标记。
7. 用单引号代替双引号
由于PHP会对双引号中的内容进行变量搜索,为了避免这种搜索带来的性能影响,程序员应该使用单引号引用字符串。
8. 转义输出
应该在htmlspecialchars函数中使用ENT_QUOTES参数,保证单引号(')也可以被转义。尽管没有规定必须这样做,但这是一个好习惯。
9. 使用逗号隔开字符串输出
字符串连接符(.)可以将单一的字符串传递给echo语句进行输出,与之相比,逗号可以实现echo语句中字符串的分别输出,这对PHP来说是一个性能改善。
10. 在输出前检查传递值
应该记得在输出前检查$_GET['query'] 的传递值。使用isset函数或是empty函数可以检查变量值是否为空。
【转】正则表达式之贪婪与懒惰
正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息。它利用约定俗成的字符结构表达式来发生作用。不幸的是,简单的正则表达式对于一些高级运用,功能远远不够。若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式。
所有能多次限定的正则运算符都是贪婪的。他们尽可能多地匹配目标字符串,也就是说匹配结果会尽可能地长。不幸的是,这种做法并不总是我们想要的。因此,我们添加“懒惰”限定符来解决问题。在各个贪婪运算符后添加“?”能让表达式只匹配尽可能短的长度。另外,修改器“U”也能惰化能多次限定的运算符。理解贪婪与懒惰的区别是运用高级正则表达式的基础。
贪婪操作符
操作符 * 匹配之前的表达式零次或零次以上。它是一个贪婪操作符。请看下面的例子:
preg_match( '/<h1>.*<\/h1>/', '<h1>这是一个标题。</h1> <h1>这是另一个。</h1>', $matches );
句点(.)能代表除换行符外的任意字符。上面的正则表达式匹配 h1 标签以及标签内的所有内容。它用句点(.)和星号(*)来匹配标签内的所有内容。匹配结果如下:
<h1>这是一个标题。</h1><h1>这是另一个。</h1>
整个字串都被返回。* 操作符会连续匹配所有内容—— 甚至包括中间的 h1 闭合标签。因为它是贪婪的,匹配整个字串是符合其利益最大化原则。
懒惰操作符
把上面的式子稍作修改,加上一个问号(?),能让表达式变懒惰:
/<h1>.*?<\/h1>/
这样它会觉得,只需匹配到第一个 h1 结尾标签就完成任务了。
另一个有着类似属性的贪婪操作符是 {n,} 。它代表之前的匹配模式重复n次或n次以上,如果没有加上问号,它会寻找尽可能多的重复次数,加上的话,则会尽可能少重复(当然也就是“重复n次”最少)。
# 建立字串 $str = 'hihihi oops hi'; # 使用贪婪的{n,}操作符进行匹配 preg_match( '/(hi){2,}/', $str, $matches ); # matches[0] 将是 'hihihi' # 使用堕化了的 {n,}? 操作符匹配 preg_match( '/(hi){2,}?/', $str, $matches ); # matches[0] 将是 'hihi'
一个外国程序员12小时的编程生活的记录
外国的一个程序员用相机每隔10秒钟拍摄一张图片,一共拍摄了4000多张,连续记录了他一天12小时的编程生活。从视频中你可以看到,这个程序员一会编程,一会看电影,一会打游戏。工作娱乐两不误。
张小龙:如何把产品做简单
今天从一个小故事开始讲:为什么苹果的手机只有一个按钮?
(台下:我觉得对于用户来说,只有一个按钮不会有太多的干扰,只要从这里开始,其它的菜单都在这里面了,这是我个人的一些想法。)
上次在分享的时候 Martin(腾讯总裁刘炽平,编者注)也在,他认为是因为这一个按钮很容易坏掉,所以用户要不停去换新的手机。这也有一定的道理,因为我上个苹果手机的按钮就坏掉了,后来没有办法只好又换了一个。
(台下:乔布斯是想说我的产品是我来引导用户使用,所以只有一个按钮的时候,你必须照我的操作,你只能按这个按钮来一步步操作。)
那两个按钮为什么就不能引导了?
(台下:多一个就多一个选择了。)
就不给他选择?
(台下:对,因为你要照我的思路来操作。之前看了《乔布斯传》,我感觉乔布斯性格上有一点偏执,追求一种极致的简洁,可能跟他的理念有关系。他如果能用一个按钮来实现,绝对不会用两个。)
那能不能不用按钮?
(其实大部分手机都不用按钮,但是可能这个按钮还是必要的。)
最重要的不是回答的正不正确,主要是看有没有一个自己的想法,任何理由都可以。
(台下:简单。)
对,简单是个很好的回答,非常好。这个问题其实没有一个标准答案。
(台下:我想补充一下,如果死机的话可能会把手机摔了,按钮可能是一个发泄的入口。)
发泄用的?
(台下:如果死机的话你会把它摔了,所以用户要去点。)
对,这也很合理,因为发泄很重要。没有标准答案,我说的答案也是一个仅供参考的答案,大家不要当真。为什么只有一个按钮?你再看一下为什么是白色的?其实白色的比黑的更酷一些,对不对?白色的其实是苹果最想做的,当时是供应商做不到,所以就做了黑的先来应付一下大家,后来才做出了白色的。白色的手机,再加上一个按钮,你会想到什么?一个白色的东西加一个按钮在上面,并且一按就会有“奇迹”发生。
(台下:像马桶。)
对。我看过一个故事,苹果的首席设计师叫乔纳森·艾弗,他以前是做马桶设计的。一个设计师的设计经验会延续,所以你可以想到这里面包含了一些历史的经验。我们经常看到一些马桶上面有两个按钮,那个体验就不好了,你每次冲水都不知道该按大的按钮还是按小的按钮。
当然,这是我开的一个小玩笑,不是真实的东西,但这个玩笑里也有一些故事,这个故事就是艾弗设计师以前确实是做日用品的设计,当时他的很多积累是来自于工业用品。然后到苹果以后,后来乔布斯回到苹果,发现他的设计理念跟自己很接近,才留下来一起来做。
我们现在用的很多是苹果的产品,给我们很多启发,所以苹果为什么硬件这样做?软件这样做?其实有很多值得思考的地方。我自己也看了《乔布斯传》,觉得它没有把苹果的一些设计思想和精髓写出来。在iPhone发布时,他说我们领先其它手机5年,这个5年领先在什么地方?iOS的设计理念是什么?它的哲学是什么?这其实很值得去思考。
从微信“摇一摇”看简单就是美
故事就讲到这里,我们开始今天的正题,先用简单的思维开始。这句话大家都听了很多——“少就是多”,为什么少就是多?为什么简单就是美?为什么复杂就不美了?
微信我相信男生都用了,女生用了也不会告诉我们。我摇到了一个叫Tina的,在三公里以外,如果大家想加我的话可以一起摇一下。(演示微信摇一摇功能,编者注)但是深圳的同事,你们在100公里以外就加不到我了。1、2、3,摇!必须在3秒之内摇,然后我们会看到一个列表,刚好我们摇的人就在这里面了。我们看到这个列表里面有十几个人,就是我们刚才一起摇的人,基本上都能捕捉到。
大家可能已经在讨论这里面的技术问题了,技术问题我相信不是一个问题,对于腾讯来说非常容易做到。我这里想说的是,作为一个产品功能,我们为什么要这样做?这个功能非常简单,优秀的开发同事可能一两天就可以开发出来,但是我们怎样把一个功能做成一种极简的体验,这个难度非常难。
你可能今天看到摇一摇的功能很简单,我们要做也很容易,可问题就在这里:如果面对一个功能,我们能做到别人还没有这样做过的东西,这是非常难的。这里是有一些方法可以遵循的,也就是简单是美的方法。我们看一下里面体现出来什么样简单的特点。
摇一摇界面里没有任何按钮和菜单,也没有任何其它入口。现在下面有一个菜单可以拉出来,显示上一次摇到的人,这是我们的一个败笔,正准备把它给取消掉。也就是这个界面没有任何东西,只有一个图案,就像是iPhone或者马桶只有一个按钮。它只有一张图片,这张图片只需要用户做一个动作,就是“摇一摇”。这个动作非常简单,是人类有史以来最有启发性的一个动作。我因此研究过人类的起源,人类为什么会直立行走?因为人类要用手来抓石头打猎,最后脚就用来做别的事了,最后就直立行走了。
我们内部开发这个功能时把它叫做“(录一录)”(音译,怀疑应为“撸一撸”,编者注),服务器上开发的代码叫“Lusefor”。(也是音译,怀疑应为堕落天使路西法Lucifer,编者注)这是人类最原始的东西,最原始的东西往往就是体验最好的。
前不久我在微博上写过一句话,我们怎样体现出最原始的东西就是体验最好的。我们回忆一下在Windows时代,多任务是怎么体现出来的?我们要摁“ALT+Tab”键。在iPhone里我们只要摁两次底下这个按钮就可以了,这个简单很多。在iPad里,4个指头把它撸上去就可以了,它就可以把多任务给切换过来。这是一个从复杂到简单的演化过程。实际上ALT+Tab非常复杂,很不人性化,所以我们说Windows体验不好,MacOS体验好,判断依据是哪个更人性化或者更简单,或者更原始,它就是好的。我们买一个iPhone或iPad给一个4岁小孩都会用,这体现的是它的原始或者简单。
同样的,我们来看“摇一摇”,它非常简单,不用做任何学习。我们避免在界面里出现任何的文字解释,一旦一个功能需要文字解释,这个功能的设计已经失败了。
很多产品人喜欢在程序里加一些Tips,觉得这是一个很好的教育手段,可如果你需要有Tips去教育用户,证明也很失败,你没有办法通过功能本身让用户一看就知道。用户看了图片,他会下意识地摇一下,这时候要给他一些刺激回馈出来,所以他会听到一个来福枪的声音——我们故意找了来福枪的声音,这个声音很刺激,原来以为只有男生喜欢,后来发现女生也很喜欢,因为它代表了雄性。本来我们给女生设计的是一个“叮叮当当”的声音,后来把它取消了,都做成这个声音了。
最初的摇一摇,摇开后的背景是一个裸体女人的上半身,那是维纳斯,是艺术。但是我们的很多用户,包括公司内部的同事甚至领导说这个影响会不太好吧?我们就把它改成了一朵小花。所以到我们要放弃艺术去追求一种大众喜好的时候,其实损失就更多了。
你会看到这个过程很有意思:(摇了以后)先有一个声音,然后有一扇门打开,再合上。甚至在打开的时候,如果你想换一张图片,你可以把手指伸到缝里面去点一下,点一下可以换一个背景图——没有发现吧?
上一次Pony(腾讯CEO马化腾,编者注)很认真地给我发了一封邮件,说摇一摇的功能真的很好,但是我们要防止竞争对手抄袭模仿我们的功能。之前我们做了一个查看附近的人,然后竞争对手也做了,并且加了一个小创新在里面,叫做表白功能,这样跟我们就不一样了。Pony说为什么我们没有预先把这些该想到的都想进去,让别人想模仿的时候都没有办法再来做一个微创新?我说微创新是永无止境的,别人总可以加一点东西来跟你不太一样。我们这个功能已经做到最简化了,别人没法超越——我们当时是有这种自信的。自信一方面是说我们已经最简化——因为就像iPhone只有一个按钮,除非你做一个没有按钮的手机——这里只有一个动作,甚至连按钮都没有;另外一个原因,我说这个体验的整个过程是非常严实的,它是一种人类的性驱动力在完成整个过程,没有什么吸引你的驱动力比性的驱动力会更原始——这是佛洛依德说的,不是我说的,所以这也是科学,不是一个道德低下的问题。
从这两个角度,一方面是它确实做得很简单,另外一方面它让你很爽,这个爽是来自很深层次的原因,所以我们说竞争对手无法超越。我不知道你们有没有赞同或者理解这一点。看起来很简单的东西,但是它要有一些方法或思考去达成这种简单。手机里可以体现出这种东西,因为手机可以认为是手指的衍生,是你的第六根指头。所以在手机底下,体验的突破空间非常有潜力,这跟你在电脑上通过鼠标和键盘来交互是不一样的。手机跟身体连在一起,很直接。为什么很多人喜欢汽车,对汽车那么追求?汽车是你双腿的延伸。
怎样看待简单是美?我的理解是,简单是一种审美观,它不是一种完全理性的结论。不是说我们尽可能做得简陋一点,而是说你脑袋里是不是有一种观念在这里——你看到一个界面,一看它密密麻麻铺满了按钮,你就知道这东西一点都不美,想要把它给简化一下。这种审美很难解释了,不多讲。
产品经理应该像上帝那样了解人性
我记得看过一句话:我们喜欢简单,因为上帝创造宇宙的时候,定下来的规则也非常简单。这句话好像是开普勒说的。有没有谁记得开普勒定律?很多物理学家会说发现宇宙的规律很简单,既然宇宙的规律都这么简单,我为什么要把很多事情搞复杂?
为什么说产品经理是站在上帝身边的人?一是我想奉承一下产品经理,赞美一下大家,另外一点是说大家很像上帝。上帝是什么样的人?他建立了简单的规则,然后让这个世界演化。
我想表达的是,产品经理和上帝一样也会俯瞰芸芸众生,知道他们的欲望,然后给他们制定一些规则,让他们按照这个规则来运转。当你做到这一点,你就会像上帝一样,会有上帝的成就感。
首先你要了解人们的欲望,然后通过你的产品去满足,并且他们使用的过程是按照你的预期来的。你知道他们会怎样自己去演化,然后你就在旁边看热闹就行了。
就像我们只做了一个很小的功能“摇一摇”,然后每天有几千万上亿次摇动在发生。我们知道这耗费了很多能量,所以我们下一步的计划是在摇动的时候做一个能量收集器,能够把摇动产生的动能收集起来用于发电,然后把它做慈善捐出去。(应该是开玩笑,编者注)因为每天浪费了太多的生物能,很多(男)人都在做无用功,其实他又找不到女孩子。我们后台的数据显示这种搭讪的成功率是很低的。但是无数的男性在那里拼命地摇啊摇,还有很多女性在那里摇,你们知道为什么吗?如果你们对女性的心理研究不透彻的话,你就损失了一半的用户。所以我们的产品经理现在都在看一本叫《女性的起源》的书,我推荐你们也看一下。我们(微信)有一半的用户是女性。
女性为什么会摇呢?
(台下:检验魅力值。)
说得非常对。这里说一个小插曲,它可以体现出产品经理对女性的无知。当时做“摇一摇”的人总以为我们会让女性用户受到骚扰,我们很对不起她们,我们为了让这个功能用起来真的对不起这些女用户,会有负罪感。
发布第二天,我赶紧给女同事说,是不是有很多人骚扰你们,跟你们打招呼?跟我想的相反,她们说我们每个人都在底下看谁收到的“打招呼”多一些。
所以我们千万不要说对用户很了解。当然我们隐私做得非常好,她会收到打招呼,但是她不会被骚扰,这些是我们做得好的地方。
你必须要像上帝一样知道用户的心理,并且知道用什么样的规则去引导他。为什么这么说?规则是很简单的,只有简单的规则才可以演化出非常复杂的事情。所以我很不认同很多产品,一开始就做一个复杂的规则,最后没有任何演化的空间。我们看到很多产品比如Twitter都非常简单,它的规则简单到你们都瞧不起它,但是这样的东西是最有生命力的。如果谁一上来给我拿一个产品计划的逻辑,我要花一个小时才能看懂,那一定不是好产品。
我们再回来看,演绎乔布斯说的那句话:我们要让用户保持饥饿,让他们保持愚蠢。看起来是在嘲弄用户,对吗?(20世纪70年代,《Whole Earth Catalog》停刊号封底有一行字“Stay Hungry, Stay Foolish”,后因为被乔布斯引用而著名。编者注)
但如果你在做产品时没有这种信心把握住用户需求,没办法控制他每一步所要做的方向,那你就控制不住这个产品,这个产品就已经在失控之中了。作为产品经理,一方面自身要保持饥渴,保持一个觉得自己很无知的状态,但对我们的用户来说,我们要想办法让他们知道他们的饥渴在哪里。
第二点,我们要满足他们的“贪嗔痴”。贪嗔痴是什么?佛教说人的本性是贪嗔痴,佛认为所有的人是瞎子,是无名的,而睁开了眼的人就是看到了光明的人,觉悟了的人就叫佛。佛教认为有三个基本的约束力使得普通的人不是佛,就是贪嗔痴:贪是贪婪,嗔是嫉妒,痴是执着。
我们要洞察这一点,因为我们的产品对用户产生黏性,就是让用户对你的产品产生贪,产生嗔,产生痴。
腾讯给大家各种“钻”(腾讯会员与特权体系,编者注),“钻”体现了什么心理?其实都体现了人性的这几个弱点。各种黄钻、绿钻,他会贪,他要升级;他会嗔,他会跟人比较,说你的钻比我的等级高,所以我也要升上去;他会痴,觉得我一定要把所有的钻给收齐。不是说所有的东西都要去做“钻”之类的东西,即使是一个体验好的产品,就像苹果的手机,同样会对用户产生这样的吸引力,因为这是人性本身的共同弱点。
所以当我们在做一个产品的时候,我们在研究人性,而不是研究一个产品的逻辑。
逻辑本身没什么好研究的,开发的同事可能更懂一些,但最重要的是“你的产品为什么是这样子,而不是那样子”,最后是对人性的分析。所有的产品都需要有人性的研究。为什么要有白色的iPhone,为什么iPhone只有两种颜色?你可以对每个好产品都提出问题,它背后肯定都是从人性的角度出发,而不是别的。所以乔布斯曾经说过一句话,记者问他:你们推出新产品的背后是做了很多用户调研吗?还是用别的方式来知道我们应该做什么产品?乔布斯说我们不需要去做调研,不需要看统计数据,但我们知道用户心里需要什么样的东西。这个知道并不是说你去问一万个用户,而是你对人心的洞察或者人在内心里的一些渴望的洞察,那你就会知道了。
什么是产品体验?总结一个字就是“爽”,两个字是“好玩”。事实上如果我们问用户为什么喜欢用微信,没一个人会说它可以省钱,或者是很方便地发短信。他们会告诉你这个东西挺好玩的,或者用起来挺爽的。这会超出你的预期,你会觉得本来是做一个通讯工具,但用户不这么看。用户看的重点和你看到的是不一样的,你在拼命地说我帮你解决了通讯问题,用户说其实这不是我关心的。如果出现这种很大层面上的偏差,就很麻烦了。
有一次我在一个聚会,来了好几个女生,我跟她们说装一个微信吧,她们问为什么。我说装微信可以免费发短信、发图片,省了彩信的钱;还可以直接说话,连打字都不需要。这几个女孩没有一个有兴趣,很奇怪地看着我,问:“我为什么要用这个东西?”然后我现场给她们演示了一下,说你看附近有很多美女,我可以跟她们直接打招呼,这些人立马两眼放光,一个个拿出手机让我帮她们装。我当时都很震撼,这两个差别会这么大。我承认我当时对人了解太少了,但是真实的结果就是这样子的。
所以当我们的竞争对手360做了一个“口信”,他们的定位很清晰,因为他们很善于做用户需求把握,觉得越实在的产品、对用户有实惠的产品,用户就会越喜欢。事实上很多产品之前也是这么成功的,所以他们做口信也是按照这个思路,说我帮你省了短信费,可以集成到一起。我看了以后就窃喜,因为我知道这条路是不对的。很多时候,用户在你这里省一点钱干什么?他会去买别的东西,奢侈品什么的,他的钱总是要花掉的,他要的是一种很爽的感觉。
所以我们做产品要找到用户心理诉求的本质。本质是什么?可以简化一下,比如对微博来说,用户上微博干什么?用户上微博的原因是为了炫耀,是因为害怕孤独,不是利群而离群,是用它有追赶。(“有追赶”是音译,不一定准确。编者注)前面大家可能明白,后面这个“有追赶”会比较难理解。想一下你们在微博上干什么?
很少有人能做到这一点,你写一句话的时候脑袋里想到哪些听众会看到,你会为他们写东西,你会构筑一个自己的形象,你会想我写了这句话以后别人看了我的形象是不是又朝我想要的方向变了一点。你会看到一个人爱上你了,或者给你发私信了,马上第一时间去看一下。你会很在意。
实际上微博是构筑另一个自我的地方,就像我们平时在生活里通过各种行为来构建一个自我。那里面爆出来很多人心的缺陷,一个内心强大的人是不需要写微博的,比如韩寒不写微博。韩寒前两天写了一篇文章,他说他为什么不写微博的原因,我当时说了一句话:韩寒对微博的用户心理分析得这么清晰,他要不做互联网产品是中国互联网事业的一大损失。
如果做微博的人对于用户为什么写微博的心理不能分析得很透彻的话,那我们就是在一个很肤浅的层面上来做产品。所以微博是一个很有意思的主题,我们自己写微博的时候,你观察一下自己的动静也发现挺有意思的。自言自语为什么做不到?因为没有人会写日记写那么多,那个才是自言自语。一旦有人群的地方就不是自言自语了,那挺难的。
群体用户与个体用户
凯文·凯利的《失控》我给很多人推荐。这本书很厚,所以很多人都没有耐心的看完它,我自己也是,可是如果我们面试一个大学生,他告诉我他看完了这本书,我肯定就录用他——不过他们不知道这个秘诀。如果做互联网产品的不看一下这本书,我认为知识是不全面的。他从生物学、社会学的角度描述了一种群体效应,总的来说,结论是群体的智商低于个体智商。这个观点不在那本书里,而是在另外一本书里,我不记得书名。一个人在组织里,组织的平均智商是低于个体智商的,个体的智商更高一些,群体会拉平这个智商。比如说在微博里,微博上多了你的智商会降低,大家认同吗?你没有发现这个变化,因为你每天降低一点。
很简单的,你每天在微博上说的话,你会发现跟大众越来越一致,别人在说什么,你也在说什么。你不会说一些大家不能理解的东西了,因为你会觉得那很突兀,会让大家不舒服。所以微博的转发那么高,自己写的那么低,就是这个原因,大家拼命的一团和气,互相夸两句。群体有趋同性。
另外一个观点是群体是一个完整的生命。我们小时候都看过蜜蜂怎么飞,有一堆蜜蜂一起飞,在这里大家忽略了一个有趣的事,就是这堆蜜蜂谁是领头的,谁来组织它们。一堆蜜蜂有一个目标,它们会统一行动,但是每一个蜜蜂都是个体,没有一个领导,不像我们公司还分出一层层的管理干部。那么谁在指挥这一群蜜蜂?趋同性让他们统一,但他们表现的像一个有生命的独立个体一样,会朝一个目标一起过去,没有人发号施令。
一群蜜蜂体现出单独个体的特征,有一个独立个体的属性,虽然这个属性从哪来没有人知道。同样的道理,一群人会体现出一群人的属性,跟个人是不一样的。如果你到前线当了一个战士,你可能对杀人这件事情会很麻木,完全变了一个人,因为那个群体都是那样的,那是一个群体性。
群体有群体的运作规律。为什么我们要说群体?因为这和我们的产品很相关。我们的产品是什么?我们只要找几十号人开发一个东西,然后我们会给几千万、几亿人来用。这些用户是一个群体,而不是一只蜜蜂,不是一个人。
很多年前我们写软件时,针对一个客户来写,这个客户要什么我就给他做什么。但现在不是,现在你的客户是谁?你的客户是一群蜜蜂,不是单个的蜜蜂。你决定一个单个个体的需要,不代表群体的需要。
第三点,群体的含义是很难预测的,因为没有人知道群体性从哪儿来。所以我们对这种群体的反映,做产品就是一个群体反映的引发器。对群体反映,我们应该去试验而不是去策划。
如果有人告诉我说我们做了一个产品规划,把未来半年或者一年的版本都计划好了,那一定是在扯淡。三个月都做不到,更不要说一年以后的计划。同样,如果有一个产品经理信誓旦旦地跟我说,做一个东西一定会在用户里产生一个什么样的效果,多半也是不可信的,因为群体的效应是很难预料的。你会很难预料往群体里面放一个产品,它们自己滚动会形成一个什么样的东西。
就像漂流瓶。(QQ邮箱和微信中的一个功能,编者注)现在漂流瓶很火,火到它成为一个独立的社区——这是从用户的使用覆盖面来说,邮箱1/3的活跃用户是它贡献的。但是漂流瓶很简单,我们做时其实不知道它会产生什么样的效果,是预料不到的。漂流瓶是一个很简单的东西,就是一个人扔一个瓶子,然后有人很弱智地回答了并漂回来。但是我们把它放在一个海量的用户群体里,它会产生一个群体效应。这个群体效应是不可预测的,大家千万记住这件事。虽然我们可以凭经验、感觉它会怎么样,但如果要很客观地预测是不可能的。漂流瓶是群体效应一个很典型的例子。所以这里鼓励我们有很多思路,应该多去尝试,而不是去分析。
产品规则越简单,越能让群体形成自发的互动。漂流瓶很简单,如果规则太复杂,把复杂的规则放到一起,用户反而不知道怎么样用这个规则互动起来。只有简单的规则用户群才能很好的互动,但并不是说规则简单就一定会传染开来。这里存在一个引导的问题。我们要做的工作是在群体里做一个加速器、催化剂,是做这一类的工作,而不是把这一块钉死了之后,用户进来以后,只能怎么样,一步一步的走。
产品定位和人性化满足用户
用一只蝴蝶引发一场风暴,虽然这样的事在自然界非常少发生,但理论上也是有可能的。所以我们更多的聊一聊这个功能,觉得挺自豪,因为它是非常简单的一个东西,但是在用户里面引起了一场“风暴”。
产品定位很重要,我们说很多时候产品经理做的是功能而不是定位。功能是做需求,定位是做一种心理诉求,也就是说定位是更底层的一些心理供给。
最后说我们做的东西,能够把它做到底层的需求。就像我们做微博,如果只是说我们要打开微博,跟用户无关系,跟用户的心理动机无关。如果我们说微博能够满足用户的心理诉求,让他获得安慰感、排除孤独感,让他获得成就感,让他在里面更加自信,让他在里面像一个敌人一样,虽然这些诉求对用户来说不一定是好事,但是他自己意识不到。
定位很重要,所以我们在微信的3.5版本里做了一个换界页面,这个页面写的字是“微信不只是一个聊天工具”,它还是别的。其实当时想写的一句话后来没敢写上去,当时想写“微信不是QQ”,然后再翻一页,“微信真的不是QQ”,再翻一页,“微信确实不是QQ”。后来觉得我们还是要温和一点,不要这么急。后来又想了一个方案,“微信不是一个聊天工具”。这句话很激烈,但是用户看不懂,说不只是一个聊天工具。然后翻几页到最后,我们会说“微信是一个生活方式”,而不是说它就是一个聊天工具,这就不一样。到了最后一页,我们就说开始我的“信”生活,是微信的信。然后他一点就会进入到一个很神秘的空间里面去了,这有一个“洞房”,其实值得看一下,就会有一扇门打开了,然后一个页面就出来了。
通常我们做软件:一个换页,此版本功能更新,1、2、3、4、5,然后尽可能把一些技术指标罗列在上面,告诉用户说我们这个性能又增强了,格式又多了,下载的速度又从每秒钟多少K提升到多少K了。
我们总把用户当做技术专家来看待、当做机器人来看待,但是用户要的不是这个东西。所以在产品里,我们一直要坚持的一个原则是,尽可能不要把技术指标暴露给用户:会禁止显示正在下载,每秒钟多少K的这些数字,“下载”两个字样尽可能不显示。
同样,我们在做一个“what’s new”的时候,为什么要做一个新功能介绍?用户真的对你的功能、特性感兴趣吗?虽然这是大家的日常工作,但用户不需要。用户要的不是了解你的参数、特性、技术指标这些东西,用户要的是你给他提供了什么新的体验。
比如微信第一次你可以使用透明背景动画表情来表达你此刻的心情,我们告诉用户的是这可以表达你的心情,而不是说我们做了一个很牛B的动画表情。用户要的不是动画,用户要的是我可以表达心情,这点是最重要的。虽然我们这里面包含了很复杂的技术,但我们把它隐藏起来了。很复杂的技术是什么?从技术的角度我也挺自豪的,因为这是全世界第一个可以把任何GIF的动画,从背景里面抠出来然后放到这个屏幕里去,屏幕可以设任何背景。GIF动画只有动的东西,背后全是透明的,是这样一种效果。就不会有一个方框,框里有一个背景,动画只能在这里面——这是QQ表情的做法。
但我们即使做了这么强的技术,我们也不会跟哪个用户说,你看我们这个技术很牛。我们会告诉什么才是他要的,是表达他的心情。我们宁愿把那个隐藏起来,告诉用户说你可以和朋友玩剪刀、石头、布的游戏,它是可以玩的。
有的时候我们程序里肯定有bug,当产品做得很好的时候,我就容忍这种bug。我说有bug也是人性的体现。我们引用老乔的一句话,产品是技术和艺术的结合,或者产品是技术和人文的结合。说得非常容易,但是你怎么样在你的产品里包含这种人文或者艺术的成分?这挺不容易的。
所以对一个产品经理来说,需要提高自身修养,在技术之外的人文方面有一些自己的认识。微信有一个版本的换页里是迈克尔·杰克逊的画像,那里面还有一句话,有没有谁记得?
其实产品是可以表达产品经理自己情感的地方,如果大家用得好的话。迈克尔·杰克逊这个东西,当时是我个人主张要放上去的,因为有一段时间我在车里整天都在放迈克尔·杰克逊的音乐。听起来很流畅,节奏很快,你一听到那个节奏就会越开越快,所以经常会超速。但它确实非常流畅,以至于我觉得我们产品,很大的程度上流畅度是由迈克尔·杰克逊的音乐带来的启发,所以我觉得一定要感谢这个人才行,感谢的冲动非常强烈,我说一定要放上去。
放上去后,正好我们以前也放过迈克尔·杰克逊的这一句话:“如果你说我是错的,那你最好证明你是对的。”为什么放这一句话?其实当时很多评论家老是批评我们,你们这里做得不好,那里做得不好。我觉得用这句话来回应这些评论家挺好。
所以你看,如果你把你个人的情感包含到产品里面,是一件很爽的事情。但我们不要把人机器化,也是这样一个观点。这种例子在我们产品里太多见了,我们太多产品太机器化了。我们动不动就告诉他这个图象有250K。我用了一下微博,看微博的点图片下载,正在下载85.97%,精确到小数点后面两位,这些都是不人性的体现。
再举一个例子说人性化的用户交流——如何把邮箱系统管理员改为具象的产品经理。
我们邮箱里经常会发一些系统邮件,管理员的邮件给用户。事实上我相信很多业务也会发送邮件出去,提醒用户之类的。
有一天,我们把这个格式改了一下,很多用户觉得感觉非常好。什么改了一下?以前是系统管理员这样的邮件,把它去掉,改成我们具体的一个产品经理人的名字和他的图象。这样的话,用户每收到一个邮件,全部是一个邮箱的产品经理发过来的,并且有他自己的图象、名字、落款和日期。这就像有一个服务专员给你发邮件,而不是一个系统管理员,后者纯粹是垃圾邮件。
我觉得这在我们很多产品里可以推广一下,用户的被认同感非常好,用户会觉得我在跟这个产品后面的人一对一交流。其实成本很低,你就是把一个产品经理人的头像拍个照片放上去就完了。所以我们做产品,是我们和用户交流的媒介,这一点很重要。
我们都知道“物以类聚、人以群分”,很多人都在问为什么腾讯的产品这么低端、这么低龄化,为什么高端用户都不用?因为我们总是在想忽悠那一些小孩子。这里暴露出一点,很多人在做产品的时候都以为我们的用户是一些低龄用户,我们要描述他们的需求来做,然后尽可能的往幼稚这个方向去做。
我们不是这么认为的,不管做邮箱还是微信。当时,甚至邮箱都考虑过要不要专门做个幼儿版或者少年版,再做个老年版和中年版,我们认为这是没有必要的。我们只做一个版本,这个版本是老少通杀的,最后用户很认可。商务的人会觉得用邮箱很好用,然后一直到小学生,他也会觉得这个满足他的需要,他用起来很好。
我们在iPhone里也同样看得到这样的例子,所以并不存在我们一定要故意的把产品做得低龄化、幼稚化。我们自己并不是一些低龄的人,为什么要那样去做东西?如果那样做,我们引过来的用户也是那样一个群体。自然原则这里就不讲太多了,但是稍微提一下。如果举个例子来说,我会认为Windows是一个不自然的平台,这是从它的体验上来说,迈克尔·杰克逊我觉得会更加自然。虽然经常因为苹果机的用户耽误了很多时间,但是我还是不愿意切换到Windows里面去。虽然说我自己用了很多年Windows,我在Windows里也算是个开发专家,但我好几年前转到苹果平台以后,就发现Windows设计得非常不人性,这是很大的一个问题。举一个例子,在Windows里有一个专门的“程序管理器”来管理要卸载哪些程序、安装哪些程序,普通用户还专门去卸载,这个挺有难度,但是我们在iPhone里是怎么卸载程序的?常按并删了就没有了。你不会看到有一个“程序管理器”在那里,不会看到“卸载”两个字。而且你常按的时候,会发现很多图表在那里晃动,为什么在晃动?表示不稳定状态,表示你可以操作。我后来看那个解释,解释得非常好,他说表示那些表情在哀求你不要删掉我。如果你把那个图标画成一张脸,你会发现确实是这样,在抖啊抖。
这里再提供一个观点,是说我们只做一件事情,一个产品只能有一个定位,或者说一个主线功能。
我们经常会想到要做一个东西,这个东西提供两个功能服务给用户,然后我们就在屏幕上放两个按钮,这个按钮是A,那个按钮是B。我们甚至已经预估到第一个按钮会有60%的人点,第二个有40%的人点。这样的界面我相信在我们产品里面非常非常多,但这是不好的。
比如说一个Windows页面或者一个客户端页面,我们尽可能在一个界面里只有一个主要的按钮,这个按钮非常显眼。然后用户到这里基本上不用思考,点这个按钮就行了。如果一个界面里并排着两个或者更多的按钮,说明有问题了。从这个角度来说,大家可以回去检查一下我们有多少界面里并排着有两个以上按钮的,这都是有问题的,意味着这个界面以后有一个分叉路口,用户要做思考才知道下一步该做什么,我们没有给他任何的暗示,就是这样子。
就像我们经常会签署一些文件,其实体验做得很不好,因为是甲乙双方来签,你要思考一下到底签左边还是右边,你要看里面的字。这就像两个按钮一样,假如我们换一种方式,把你要签的圈起来或者标个颜色,写个字,请你签这里,你就很清晰。所以在我们的界面里也是,即使一个屏幕有多个按钮,我们会标一个默认按钮,它是绿色的或者是加重的,使得你进来以后只会点这一个按钮,其他的可以忽略掉。
所以在这里我们会经常问自己一个问题,如果你这个版本做了10个功能的替换,你想一下10个功能都上线以后,它们各自对用户带来的帮助有多大?如果你真的想这个问题,你会发现其实你做其中两个功能就够了,做其他8个功能的时间让同事放假去旅游真的会更好一些。也就是说,我们做了很多事情是浪费的,这里就提到一个“基架”的重要性。
开发的同志都知道,我们加的东西越多,将来维护的麻烦就越大,而且你还去不掉——哪怕只有很少的用户在用,但是你就去不掉,这挺可怕的。所以有时候,我们的产品经理经常是在做坏事,不是做好事,因为他拼命引入新的功能进来,后面反正又不用自己写代码就不管,然后就把开发的累死了。运维的也很累,因为东西越来越多以后,带来的问题也越来越多。后来我对待我们的产品经理方法很简单,我说我对你所有的提议不说“不”,一般是不会错的,因为这个错的概率只有0.01%、0.1%那么大。但是如果我任何一次说“可以”,错的概率非常大,往往超过80%可能是错的。单从概率上来说,真的是这么一回事情。因为我们要冒出一个想法来太容易了,但是我们要知道这个东西是否一个正确的想法,就太难了。我们开一下午头脑风暴的会议就可以冒出无数的想法。
产品如何从用户体验出发
刚才我提到其实我们的产品经理工作里很多是研究人的心理,你们肯定会问我们怎么样去研究心理,我们是不是应该有心理学的培训课程,那个挺难的。其实研究客户心理就是研究自我,很多时候我们是瞄准自我的需求来做产品的。我看到这里有Qzone的同事,也发现Qzone的产品经理都跑到别的博客里面去写博客去了,自己都不用自己的东西。不是为我这种用户设计的,那是为谁设计的呢?
当我们研究不到用户需求时,我们就会说只要让我们自己用得爽,这个是比较容易做到的一点。怎么样让用户用得爽呢?如果光凭一些体验的话,其实是有一个比较简单的方法,把自己当作一个傻瓜来用产品,傻瓜心态。
把自己当作傻瓜,这个挺难的,但据我所知乔布斯也是用这个方法,而且他这方面功力特别强,他能瞬间把自己变成一个傻瓜。我就不行,我要经过5-10分钟的酝酿才能进入到这个状况,这是非常难的一个功力。我观察公司里面有一个人也很厉害,就是Pony,他大概能在1分钟的时间内酝酿成。但是我发现我们的产品经理经常花了3天还达不到一个傻瓜的状态,所以他们总是太专家了。你要知道我们有上亿的用户,他没有这么多的背景,他们用这个东西只是第一眼的感觉或者用一次,一、两分钟的体验就决定了。
但是我们自己会把事情想得很复杂。我们Free Wifi的体验特别差,(腾讯内部的免费Wifi,编者注)你们有没有发现?特别是手机访问的话每天都要确认一下,搞到最后我就不用了,我宁愿用3G上网。所以有的时候往往好心做不了好事,就是这样子。
这是我的一个附件,是前两天邮箱新改版后昨天刚刚上线的。以前没有这个东西,现在你可以在里面直接查看你的附件,也可以搜索,看起来很方便。像这些Word文件、图片都可以直接阅览,看起来挺方便,而且你可以把所有的附件都列出来翻页。最后这是电脑做出来的图象,不是真实拍摄的,这样看附件是不是很快?对吧,所有的附件都在这里。然后用了3分钟的时间进入到一个傻瓜状态,就说这个地方做得不对,这是错的。我想请问一下你们觉得这个东西是对的还是错的?是考验大家的傻瓜发展力。这个功能挺高级的,我还可以全选、发送或者查看。这是把图片分出来,我还可以下载一个来看。有没有人觉得这里面有问题?(这段很难编辑,多看几遍就看懂了。编者注)
如果你是做这个东西的产品经理,你会很容易陷入到一个误区里去,觉得你把它做得很高级了,用户就需要了。事情往往不这样。这样的事情发生在每天的工作里,每天我都要查看很多次,所以现在感觉自己越来越傻了。但做的人不会意识到这里面会有问题,我们都以为自己的工作应该把功能都做上去,然后说我的KPI可以完成了,但这往往是违背用户需求的。
什么是一个“傻瓜化”的过程?把你自己当傻瓜的过程是说你要放下你脑袋里面所有装下的事,这个时候你就想你就是一个很初级、什么都不懂的用户,然后你来用这个东西,这个非常难做到。
如果做不到,你就拉一个用户过来,你看着他用。当我们进入到一个很傻瓜的状态,你会问自己问题,我现在干什么?我为什么要进来?我进来想完成什么?用户进来,看着附件他想干什么?他想搜索到附件,找到某一个邮件里面的附件。他需要把所有的附件列出来吗?列出来一页最多20个,然后他再翻页,他会这样浏览吗?其实不存在浏览的需要。
我们进到邮箱里为什么会浏览邮件列表?因为你一天可能收到几封新邮件,它必须按时间列在那里。但附件是你历史的东西,它可能是很早以前的,而且因为太多了,你根本不可能把它浏览完。你真正找到的时候,你也不会用预览的方式去一个个下载。对于当前页来说,我也并没有一个要分组查看的需要,虽然这个东西很高级,但问题是这个需求不存在,这是最大的问题。
也就是说我们做了很多东西,对用户来说不是他需要的,我们完全做偏了。我当时就问我们一个产品经理,说这个东西可能不是用户需要的,你们这样做了,用户也不会买账,他进来一次就不来了。然后他就不好意思地告诉我,说他们上线一天,发现用户确实没有什么增长。之前做了一个“飞度”(音译,没搞明白,编者注)也是,没有增长,没有对用户产生任何吸引力。
这样的案例在我们工作里是非常非常多的,我相信在座的可能都做过这样的事情,做的一个功能或页面弄上去,然后很奇怪,为什么我们做得这么好,用户数没增长?因为那不是用户要的东西。如果你能真的让自己进入一个初级用户的状态,并且进来感受一下,你能感受到那种初级用户的心态,那你就能看到这里面的问题所在。
能够进腾讯的,相信大家的逻辑、推理能力都很强,但很多时候逻辑、推理不能解决问题。所以这时我们应该多调查一些用户或者感知一些趋势。趋势怎么感知?用户只能对过去的事情产生认知,未来的东西才是趋势,你怎么知道下一阶段会流行什么样的潮流,那才是最重要的。我们怎么去了解趋势是什么?有很多方法,比如分析数据。分析数据当然很重要,但第一你的分析方法很容易出错,以至于得出一些错误的结论,然后还振振有词说我是从数据里分析出来的,那更可怕。
有个故事,一个飞机修理厂为了了解飞机哪个部分最容易被击中,就到飞机修理厂看,看飞机出故障主要在哪些部位,然后他们发现飞机身上主要是在机翼的部分,那里弹孔最多,于是他们决定把机翼部分加强,做得更加不容易被击伤。后来有一个人说这可能不对,因为被击中机头的飞机就掉下去了,没有回修理厂,所以你们光统计修理厂的飞机是没有意义的。数据统计也是这么一回事,严格依赖于统计数据所用的方法非常难找。(应该是指找齐所有维度并乘以相关系数,编者注)你可以按照自己的思路得出一个有利于你的数据结果出来,那非常容易。
所以有时去感知一种趋势,来自于我们的各种渠道,包括生活中的各种渠道,或者微博上的各种渠道。我自己的个人喜好是看一些论坛或者微博,去看这些离我很远的用户,他们在什么样的氛围、什么样的场景里去用我们的产品。
所以这里管人的同事们长期会坚持一个习惯,以前提过“1000、100、10”,每周要去看1000个帖子,100篇博客,做10个CE(用户调查,编者注)。所以人人都想用的产品,大概腾讯也是这样子,因为用户量实在太大了,我们要做一个小众的产品都很难。
用户是很懒惰的,我们要针对这些懒惰的人来做设计。因为懒人他不喜欢去学习,也不想多花一分钟先去了解一下,所以就像我们之前说的,如果你的产品需要有一个弹出来的Tip来告诉他该怎么做,那你就失败了,因为用户连一个Tip都不愿意去看。
现在喜欢阅读的人越来越少,大家都说微博出来以后把阅读给扼杀了。事实上,可能大家刚毕业姑且会用一下Google Reader这样的东西读一些文章,现在读得越来越少了。这并不是说大家的学习性出了问题,而是一种天性在这里。
最近我们也在准备做一些阅读类的东西,因为邮箱做了一个阅读空间。然后我们又想在微信里面,能够每天给你推荐几篇文章过来。如果我们要做这一类产品我们怎么做?因为大家都是很懒的人,我们应该专门为你们做一个不需要任何查询的阅读器、阅读产品。这个产品你进来以后不需要订阅的过程,我们可以根据你微博上的爱好分析出你的喜好来,然后自动找到你可能感兴趣的文章,自动发给你。你进来以后,也不需要看到目录——那些都是不人性的,你只要看到就像一份小报纸、小杂志一样,只需要翻页,并且只给你10篇文章,看完以后就告诉你没有了,你该休息了。这是做阅读器的一种新思路,我觉得它反而是符合人性的。相反你做一个Google Reader类似的东西就显得非常不人性了。这里考验的地方是,你这么有勇气把这个东西做得非常人性,并且做得很简单;另外你的后台技术有这个能力,真的能找到用户感兴趣的,不需要他订阅。一旦需要订阅了,体验就已经折扣了80%。
之前有一句话说web已死,大概从移动互联网的发展趋势来说,确实是有这个趋势。手机的增长非常快,手机应用的增长也非常快,可能很多人还是集中在web这块,但是这一刻,我觉得不管怎么样都要把眼光放到手机里去,因为PC的不增长和手机的快速增长,对比实在是太强烈了。不管我们是做什么样的web产品,都应该试图扩展到手机端去,而手机端的话,浏览器可能不是一个主要的入口,APP才是。
而且APP的趋势,不是要做一个大而全的APP,而是做成尽可能小的APP。为什么不是大而全的呢?因为用户很懒,我要看天气我就点天气的APP,我要看股票就点股票的APP。我不会跑到腾讯所有服务的APP里去,然后钻到里面去找天气、找股票。甚至将来,我认为每一个明星都会有一个APP,而不会在新浪微博里面,没有自己的个性。假如一个明星比如李宇春有个自己的APP,我相信她APP的用户会挺多的,下载这个APP的用户可能都是百万级的。她为什么非要到一个很多明星集合的微博里面去?
用完就走其实是Google的哲学,但是我们很多产品的考核指标是用户在这里停留的时长有多少。这是一个选择,你是选择给用户自由一些,还是把他拉到这里。我们的选择是说用完就走了,而不是说一定要让他黏在这里,因为他下次还会回来。
我们也要鼓励一种插件化的思路,这个看起来很技术化,但是对插件的架构也是这样子的。如果是插件化的,你核心的技术规则就会非常简单,而不是一开始就做一个很复杂的产品出来。好的技术人员都会有这种思维,我觉得好的产品也应该有这种思维,产品也是有架构的。很多产品设计一开始就去拉动用户,(导流量,编者注)这种拉动是不好的,为什么不好呢?因为一个产品之初是要去检验它有没有生命力,你刚做出来就通过拉动的手段,拉了100万用户进来,你就把一个没有生命力的场景装得像有生命力一样,最后误导了大家,后期又投入了很多在继续做。而且你会把那些事实有掩盖了,本来做得不好的人都以为这样是挺对的。
所以当我们做第一版出来时,应该做的事情是放一部分用户进来,比如10万用户,然后这10万用户能不能产生一个自然的增长,是不是会有一些用户口碑、用户示范的传播,那才是体现你这个产品是不是好产品,是否有生命力的一个表征。
第二个是打通、整合,这是大公司经常喜欢提的一个东西。我们都知道几个亿的产品,卖不出去了才廉价推销,(指合并到一起,编者注)也就是说如果我们不能在一个点上把两个普通的产品非要整合起来,这并不会让它加分,可能是减分的。这里鼓励的是我们在某一个点上取得突破,用户会因为这一个亮点而来用,而不是说你有两个、三个平庸的点来用,这个没有意义。
第三个,我们的习惯会说向竞争对手学习。我不知道大家有没有感受到这个氛围,平常会不会接触到这种压力:你看竞争对手做了一个什么东西,你们为什么不做呢?
(台下:有,尤其是一些商业模式。)
如果别的做得好的话,你们觉得该不该学习?
(台下:关键要看你是否比他强,如果你不比他强,你最好得学。你要学他某个好的东西,你要看你的能力。你发现你没有那个资源能力的话,最好还是他最适合做。)
但是别人的东西,已经获得用户认可了,已经取得成功了。
(台下:如果我没有这个能力我学他肯定不行。)
腾讯肯定有这个能力,什么东西都可以学过来。
(台下:关键是学他为什么做的好,而不是死搬硬套的。有一句话说“像我者生,学我者死”。)
这个话题我觉得比较刺激,在公司内部是可以引起讨论的东西。上次一个分享会,我跟一些部门的经理分享这个东西,他们说争议也是挺大的。我当时举了一个例子,在微信的竞争里,我们的竞争对手做了一个涂鸦功能,(指米聊,编者注)这个时候我们该不该做?他这个功能其实很多用户都很喜欢,很受欢迎,对吧?甚至有人说我们做了,等腾讯来抄袭。这样的地方该不该学?所以这些话说起来挺容易的,你说学其神,但是神是什么?就是那个东西,如果做的话就是一样的。
当你面对这样一些东西的时候,会怎么选择?我的观点是要尽可能站得比别人更高一些,而不是把跟随当作一种策略。所谓站的更高一些就是说不要在乎这种一时的得失,这个功能再好但它不是一个扭转战局的决定性的东西,那我宁愿不去做它也没有关系。我可以去想新的办法来做,否则的话你在气势上就会输给别人。最后承认它是一个好功能,但是我们不做它。我们同时通过一些别的方式做更好的东西,例如我们首先做了导入功能,对方就很被动,他们不知道该做还是不做。
这样的地方我相信在工作里会非常多,因为每个产品都有很多同类产品。当然也会感受到很多压力,因为来自上面的压力会说你们为什么不做?问你怎么样有信心、有方法的面对这种挑战,其实是挺不容易的,我也非常理解。但这也是我们面对挑战的一个处理能力。
我感觉这已经变成类似文化的东西了,(指学习别人产品,编者注)所以个体很难去抗衡它,但我的建议是尽可能在小地方体现这种差异化出来,即使做也换一个方式来做。
第四,按规则行事,不按规划行事。就像我刚开始说的,一个产品如果有半年或者一年的规划,那可能是有问题的。如果你跟我说微信下一个版本能做什么,我会告诉你我不知道,因为下一个版本我可能知道,但是下下个版本我真的不知道。我从来不做超过一个版本的规划。那你会很奇怪,这样做不会忘掉吗?我们会把一些可能忘掉的东西都砍掉,每做一件事情我们会很保守。从头一个版本到现在,我会认为从结果来看,版本规划的非常有序,但其实它是没有规划的。
没有规划很重要的原因是这个市场变化实在太快了,你的任何规划都是跟不上这种变化的。我们好几个功能点都起到了转折性的作用,但是这个转折性作用点都是在做之前一个星期左右的时间才做出来的。所以这里经常给人的一个感受是,3个月的感受是很危机的。你必须在这3个月的时间里去积累这种感知,到了3个月之后你会说,我经过这3个月的积累我有什么想法,哪个时候做出决定才是比较好的。
另外就是看注册用户,当然这个不用说了,因为这些对腾讯来说太容易,我们看重的是真正的活跃用户。什么是活跃用户?尽可能给自己的产品定一个严格的标准。活跃用户是什么?实际上最严格的,现在邮箱的活跃用户大概是1.2亿,但是没有任何一个别的产品会这样来定义活跃程度:它的定义是在最近的30天里,有不同的3天登陆过邮箱,并且还发过信的用户。这很复杂对吧?但很严格。这避免一个什么情况呢?因为QQ上有一个入口,很容易就进来了,只算最近30天有登陆过的用户太容易了。对我们来说定这样的目标没有挑战性。
最早的时候,我们邮箱给自己定的活跃用户就是这样一个指标:最近30天有不同的3天登陆,并且还发过信的用户才叫活跃用户。然后从100万用户开始涨,一直到现在的1.2亿。但这个指标定的非常合理的话,我们就会避免为了去拉动登陆或者拉动什么,比如跟QQ谈,你们这个报告量多一点,用户量现在大了。这点就没有意义。
这个活跃用户的指标会帮助大家真正关注用户体验,用户留下来并且愿意给人发信,给人发信是很难的。我们的很多指标如果通过比较容易,你就很容易由拉动的方法把指标给拉上来。这样就挺不好的,不是一个长线的做法。
另外一点是说用户要什么我们就给什么,这个在内部很高层的用户里面甚至也会出现。比如你的上级可能会说,你看用户给你提出这个需求了,你为什么不做?这是需求,但是这个需求不一定是对的。其实这种观点是不对的。如果用户要什么就给什么,那还要产品经理做什么?就不需要产品经理了,找一个接线员他能够接线就可以了,对吧?我可以举一个例子,你如果用到一些微信的同类软件,你可能会知道,微信里没有引入状态,(指提示给用户,信息已经发出、对方收到、对方已读的状态,编者注)这点包括很多公司内部的同事都在经常问我们。这是不是用户需求,当然是用户需求。为什么不做?因为我们觉得不能用户要什么就给他什么,要变个花样给他,用户要的不一定是对的。
如果我们针对需求一个个去满足,你可能获取了这部分用户,但是得罪了另外一部分用户。可以说我自己就挺不喜欢把我的已读状态暴露给别人,你想如果有状态,如果你的上级(通过微信)找你,你看了然后你又不回,就很麻烦。
我们要给人撒谎的机会。我们说人性是什么?给他撒谎的机会,说我没有看到。你看短信不太准确,我们经常会说,你那个短信丢了,我们没有看到。如果我们把人都像机器一样约束起来不一定是好事。
我们为什么不做已送达的状态?因为我们觉得未来的系统是绝对可靠的,我们有这个信心,肯定会送达,除非他关机了,我们不会再专门做一个是不是已送达,只有不自信的系统才会做这样的状态。而且你每发一个消息还有个已送达或者发送中,那很丑陋的,多了一个东西在那里。所以这也是一种态度。对于这种用户要什么就给什么,其实是考验产品经理水准的东西,因为我满足需求很容易,但是我怎么找到理由拒绝他,或者说找到什么方式实现它,这个非常难。
不要太过重视评论家的意见。实际上业界有很多评论家会对你做的东西写评论或者博客,但是你要知道一点,这些在电视里做股票评论的人都是炒股亏了的人。所以真正有水准的评论家应该不会到外面去写这个评论。很多时候评论家的意见我们去重视,不如我们真正了解几个用户他们是怎么想的,怎么用的,这个才是我们要关注的。
下面一条我们说避免定义复杂的逻辑和形态,这个前面已经反复强调了好多次,一个简单的规则才是上帝比较认可的。说到这个,我想说一下的是,最近邮箱的漂流瓶很火,以至于公司经常请我们的产品经理去讲课,讲关于做QQ邮箱漂流瓶的。这里有没有人听过?
有一次我和那个产品经理说,你这样老是出去讲会把别的同事都给害了,因为你们现在把事情越做越复杂,那是我们不认同的。我们做产品不应该那样做。怎么做呢?漂流瓶你会发现每周都在升级,都在放一些新的瓶子进去,放一些新的花样进去,靠这个不断刺激用户,使用户维持在一个比较高的活跃水准上。我说这已经变成一种运营性的产品了,这不是我们应该做的,这个方向不对。
所以在微信的漂流瓶里,我们不会做任何复杂度在里面,我们不会说还提供几种瓶子给你选择,然后还提供很多花样在里面玩。不会的,因为他们那样做最终的结果会很惨,每个星期都要更新,都要提供新的东西才能刺激用户。也就是说他们在把事情搞的越来越复杂,虽然最终的数据反馈还不错,总体的服务用户在增长,但那不是最好的方法。最好的方法是什么呢?是把对底层的规则梳理的更加清晰,然后能够发挥作用,而不是不断打补丁的方式。
这个可能非常难解释,我就比较简单的说一下。这里可能有些开发人员会非常理解,其实跟代码非常像,但你把代码变成复杂的系统的时候,它是有自己的结构的。产品也是,很简单的一个产品可能包含了上百个功能在里面,这些功能你可以像写代码一样,按一个线性的方式把它串起来,但是也可以做成一个很有架构在里面的东西。这是考验一个人对产品的见解是什么样的。我们心中一定要有一个产品的架构在这里,而不是说我们这个产品就是一大堆功能的集合,只是一个无序的集合,那样就很糟糕了。那样他就没有自己的骨骼和系统架构。
另外还有一个产品技能的心态,这也是我感触比较深的,就是跟人讨论问题的时候会争论起来。对产品性能来说,我觉得它是抱着一种求知的态度来讨论,而不是争论谁赢了谁输了这样一个观点。如果是这种求知的话,当别人说服了你,辩赢了你,那你很高兴,因为你接触到了新的知识。我们应该鼓励这种辩驳,但不是为了自尊心而战。
所谓自尊心而战就是我经常持一个观点,然后领导不同意,我觉得很受伤。那这个就不是一种辩驳,不是一种求知的心态了。不像代码我们验证一下就运行出了,产品方面的观点其实会容易产生分歧,这种分歧争执可能也会比较多。如果为了抱着从中获得更多一些洞察或者更多一些知识,我就不希望别人能够说服我。我举一个例子,像刚才我们一言带过的功能,比如说已读和已发送的状态,这个东西就是我们的技术总监(大卫)提出的,他是逻辑性非常强的人,跟我争论说他觉得要有这个东西,一定要有,这是我们区别于QQ或者传统IM的一个不同的地方。我认为不应该有,我们在这个点上开始争论,争论了整整3天,什么事情都没干就在争论这个问题。这样争论的结果很好,他为了说服他会做一些CE,给同事发一些问卷,然后在问卷里面列一大堆问题,通过这种调研的方式来找到证据。这样的过程我觉得非常好,大家是要给一些证明。后面两句话其实有点矛盾,这个其实不太好解释,我们就不说了。
如果我们拿微博做案例的话,它的重点用户是谁,把用户分出来,它的核心需求是什么,是一种自我存在感,就是心理诉求是什么。它的黏性是什么?是好友圈。就像我不会认同新浪微博是因为有些名人在那里,所以有人去,我认为陌生人是黏不住人的,只有朋友关系才会让你有黏性。一个陌生人写的微博再多,你几天不看你都不会觉得有什么,但是朋友们的动态你会觉得很感兴趣,所以我会认为说这种核心的黏性会在好友圈来产生。那多少好友才算多?我觉得三五个好友就可以了,而不是说越多越好。这些观点会影响你在做的产品。
就像在微信里,我们不会把你所有的QQ好友导到你的微信通讯录,也不会把你的手机通讯录好友全部导到你的微信好友里来,这个也是同样的观点:我们不认为好友越多越好。我们再回答一些问题,比如说怎样传播、怎样滚动起来,当然不是拉动,是自发的滚动。怎么样互动,怎么样有一个重点用户的突破口,从当时做的一个小的案例分析。
最后我要说的是,“我所说的都是错的”,这是我在微博里的签名。这是为了告诉大家,我今天说的大家不要当作一个正确的东西,事实上只是一家之言,大家可以想办法去找到证据推翻它。教条是没有意义的,我平时也不大喜欢到处分享东西,因为分享的效果不一定是最好的。
相反每个人自己体会到的东西才是自己的,就像我们要解释“什么是光”这个问题。如果有人生下来天生就是瞎子,你跟他解释一辈子什么是光他也不会知道,除非他睁开眼睛看了一次,那不用解释他也知道了。这对一个瞎子而言是解释不清楚的,不管你用多少语言都解释不清楚。我觉得产品里的思考和体验也是这样。
【转】如何评判一个网站的质量?
网站质量是是衡量一个网站好坏的标准,目前搜索引擎还没有公开的统一标准,但是根据各个搜索引擎的公告和一些数据的报告来看,我们可以大致评判一个网站质量好坏的标准有那些。
1:网站内容
网站内容的评判到目前还没有一个好的评价方法,它主要是靠用户进行内容的评判。而访问者的评判方法也很简单:好的内容继续阅读,不好的直接走人。
衡量一个网站的内容好坏与否,可以通过几个大致的指标进行判断:
(1),自然的链接是用户主动转载了你的网内容,一个网站上的内容如果得到了用户的认可,那么对搜索引擎来说也是非常值得收录的,并且还能算做有价值的外链。
(2),通常情况下,访问网站的用户在网站上停留的时间越长,说明网站提供的内容就越有价值,用户愿意花更多时间进行阅读和浏览。
(3),如果网站内容够好,那么用户会访问多个相关页面,我们可以称之为PV。
(4),网站的内容质量越高,那么网站的老用户数量就会越多,新用户会再次来访你的网站,也就是RV(重复访问者)。
虽然网站的内容也没有统一的衡量标准,但是可以从上述几个方面大致看出一个网站的内容建设得当与否。如果将一个精心编排的网站内容和一个靠采集、复制过来的网站进行比较,可以很明显的看出其中的区别。
而且从广告盈利角度来讲,内容质量越高,访问者越会对网站产生信任,对页面上的广告也容易信任,从而进行点击。也就是说,也可以通过某个单一页面的广告收入来判断内容的好坏。
网站存在的意义是为访问者提供有价值的内容,但目前很多站长急功近利采用采集+伪原创的方式建立一个毫无价值的网站,既不能让网站有良性的发展,同时也是搜索引擎打击的对象。
2:网站流量
网站流量顾名思义就是网站的访问量,主要是指IP、UV、PV等一些常规数据,通常用于衡量网站质量的指标是网站的PV。
网站流量是最通用也是最有效衡量网站质量的标准。从联盟广告的角度来讲,网站流量越高,那么收入也就越多。所以说,提升网站流量也就是提高网站的收益能力,同时也是评判网站质量的一个标准。
3:网站用户体验
网站有良好的浏览体验包含以下几个方面内容:
网站具有清晰的层次结构。
网站有良好的性能:包括浏览速度和兼容性。
网站的广告不干扰用户的正常访问。
合理设置网站的权限。
做好用户体验也就是用户对网站的黏度。网站的用户体验设计的越好,代表来访者越喜欢网站,用户能方便快捷的找到自己需要的信息,也更愿意多在网站上停留,对站长来说,如何提高用户体验是网站的头等大事。
搜索引擎优化讲究的是对网站功能、网站结构、页面布局、网站内容综合性的设计,而用户体验讲究从网站印象、网站功能、网站实用性、网站内容等综合因素来改善网站环境。合理的推广与提高用户体验,是提高网站转换率的重要因素。
其实用户体验是非常宽泛的一个概念,应该说任何有利于用户的相关设计都可以叫做用户体验。