bibodeng

Think By My Mind


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

持续学习的必要性

发表于 2017-03-09   |   阅读次数
经过李笑来的点拨,我们越来越多人是以是否能给自己带来成长来考虑问题了,比如说换工作,或者是做一个重大决策。将成长的必要性提高了,你会以它为价值观问自己一个问题,这件事能否带给你成长?如果一件事情的答案是能,那么就大胆去做吧。都说,投资自己是最好的投资,持续的学习、进步给人带来的变化是巨大的。

知乎上有个问题“你最牛逼的自学经历是什么?”,其中有一个答案就是讲述自己对于计算机什么都不懂,到精通前后端,薪资涨了几倍的故事,而这一切只用了两年多。从一开始asp是啥都不知道,再慢慢了解HTML,CSS,JS,然后深入学习了JavaScript,看了一些经典的书,软件实战的多了,想写出更加优雅的代码,于是学习了设计模式,深入了解了数据库技术,这些都是到了一定阶段自然而然的变化,因为了解的更深,做得更好,必然要了解下一个阶段要了解的东西,他的这两年可以说是超过了许多平庸程序员的十年,可见成长的力量有多么巨大。

假如说,成长是你的一笔投资,你用时间和精力来换取你在技术上的造诣,那么持续地在成长上投入时间和精力,你的”资本“会变得越来越雄厚,就像你投资一个增长很快的股票(技术或造诣),而且不断有额外的资金(自学)投入,那么你的成长将是飞速的,能够大大缩短你达到预期水平的时间。另一方面,成长是一种正反馈,强者愈强,你学到了一个东西,它能够为你创造更多的收入,同时还会提高你的效率,那么你又拥有了更多资金投入到下一波成长中,相当于达到更高水平,只要更少时间,这样久而久之,你的水平会加速提高。

另外,比起10年的平庸,不如2年的密集的成长,质量比起时间长度,会更加重要,一个年化5%的股票,比起一个年化30%的股票,差的不知道哪里去了。所以我们应当注重自己时间的质量,和投资策略,那就是持续投资自己的成长,注意是持续,不是一天两天,也不是一个月两个月,而是一直这么下去,除此之外,在学习和做事的过程中,认真起来,认真得久了,你就赢了。遇事多想一个”然后呢?“,通过这样提高思考的质量,慢慢地你时间的质量就会提高,同一段时间内,你能做的事情更多了,进入一个良性循环。


Ubuntu环境下迁移MySQL硬盘数据

发表于 2016-12-13   |   阅读次数

Linux的文件系统可谓是非常地直观,一切设备挂载之后,都是一个文件,如果说我们的机器硬盘容量不够了,给扩充了一个硬盘,那么如何将MySQL中的数据库安全迁移到新的磁盘上呢?其实借用Linux的软链接方式只需要简单的几步,就可以将数据移动到新磁盘,但却对程序透明,从而做到无感扩容。

##挂载硬盘并格式化
将硬盘安装到电脑上,或者是在购买了云服务器的硬盘,那么接下来就应该将它挂载在系统的某个位置,才能开始存储文件。进入终端命令行,运行fdisk -l 查看数据盘的情况,这个时候应该会发现你新安装了一个硬盘,假设是叫 /dev/xvdb,那么按照流程给该分区进行预分区:

运行 fdisk /dev/xvdb,对数据盘进行分区。根据提示,依次输入 n,p,1,两次回车,wq,分区就开始了。
fdisk-1

运行 fdisk -l 命令,查看新的分区。新分区 xvdb1 已经创建好。如下面示例中的/dev/xvdb1。
fdisk-2

运行 mkfs.ext3 /dev/xvdb1,对新分区进行格式化。格式化所需时间取决于数据盘大小。建议采用更新的ext4,一般较新的Ubuntu都支持ext4了。
fdisk-3

运行 echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 写入新分区信息。完成后,可以使用 cat /etc/fstab 命令查看。运行 mount /dev/xvdb1 /mnt 挂载新分区,然后执行 df -h 查看分区。如果出现数据盘信息,说明挂载成功,可以使用新分区了。

##转移数据
假设现在我们要将mysql的数据库内容,即/var/lib/mysql/下的内容迁移到/mnt/mysql/目录下

先停止mysql

service mysql stop

移动数据:
安全起见先使用cp,完成之后再将/var/lib/mysql/原本删除

cp -rf /var/lib/mysql/* /mnt/mysql/
chown mysql:mysql /mnt/mysql

删除/var/lib/mysql/目录

rm -rf /var/lib/mysql/

创建软连接:

ln -s /mnt/mysql/ /var/lib/mysql/

##配置工作
这个时候,尝试启动mysql

service mysql start

如果存在报错的情况,很可能是迁移之后数据库程序没有权限访问新的文件夹了,具体的报错原因可以找/var/log/mysql/error.log,如果真的是:

InnoDB: The error means mysqld does not have the access rights to the directory.

则说明真的是没有权限,需要我们人工配置一下,编辑mysql的一个配置文件:

vim /etc/apparmor.d/usr.sbin.mysqld

在配置文件中/var/lib/mysql/** rwk 下方添加两行:

/mnt/mysql/** rwk,
/mnt/mysql/  r,

授予程序新目录的访问权限。再次启动mysql,即可看到成功信息:

service mysql start

后面即可连接mysql进行数据库操作了:

mysql -uuser -p

ubuntu环境下安装比特币软件

发表于 2016-12-01   |   阅读次数

windows及mac等环境安装比特币软件方法不在本篇阐述,本篇介绍如何在ubuntu 14.04环境下搭建比特币的核心程序。

##准备
首先,添加比特币的程序PPA源

add-apt-repository ppa:bitcoin/bitcoin
apt-get update

如果你支持bitcoin unlimited,支持更大的区块,想安装比特币的unlimited版本,则将以上语句换成:

sudo add-apt-repository ppa:bitcoin-unlimited/bu-ppa
apt-get update

还需要安装一些依赖:

apt-get install libboost-all-dev libdb4.8-dev libdb4.8++-dev

##安装
安装很简单,只需要执行下面命令:

apt-get install bitcoind bitcoin-qt 

##运行
将二进制程序纳入到path所涵盖的目录中,这样无需输入完整路径就能够运行程序了。

ln -s /usr/bin/bitcoind /usr/local/bin/bitcoind

我的系统将bitcoind安装到了/usr/bin/bitcoind了,以上是将/usr/bin/bitcoind 软链接到 /usr/local/bin目录中,也可以不链接。
运行之前,先创建一bitcoin目录,添加一个配置文件bitcoin.conf,bitcoind运行时自动会读取配置文件。

cd ~/
mkdir .bitcoin
cd .bitcoin
vim bitcoin.conf

编辑配置文件的内容:

server=1
daemon=1
testnet=1
rpcuser=UNIQUE_RPC_USERNAME
rpcpassword=UNIQUE_RPC_PASSWORD

rpcuser和rpcpassword可以任意填写,但是二者不能相同,testnet表示同步的将是testnet的数据,去除该配置,则从正式环境比特币区块链中同步数据,具体的配置文件模板可以参考bitcoin.conf模板
配置文件写好后,则可以直接运行比特币的服务程序了。

bitcoind

##测试
比特币核心节点运行起来后,我们可以使用bitcoin-cli来跟bitcoind通信,获取其中同步的数据,运行下面命令,可以测试比特币核心是否运行起来了。

bitcoin-cli getinfo 

getinfo可以看到类似如下信息,表明了比特币核心的运行情况:

    {
  "version": 130100,
  "protocolversion": 70014,
  "walletversion": 130000,
  "balance": 0.00000000,
  "blocks": 151998,
  "timeoffset": 0,
  "connections": 8,
  "proxy": "",
  "difficulty": 1203461.926379965,
  "testnet": false,
  "keypoololdest": 1480560958,
  "keypoolsize": 100,
  "paytxfee": 0.00000000,
  "relayfee": 0.00001000,
  "errors": ""
}

通过尝试,你可以使用更多命令,如getblockhash, getblock, getrawtransaction 等,具体的命令列表参考 bitcoin rpc命令

祝玩比特币愉快~

一位面试官的心得

发表于 2016-10-14   |   阅读次数

之前自己一直是被试者,对于面试这块更多是从技能和自己的表现来分析整场面试,然而最近我从被试者的身份转换成了面试官,所以整个视角又会有所不同,而且我感到很有意思的是,为了保证企业录用的人才符合自己的要求,面试官是需要用很多手段去测试被试者。

开始面试前,企业要有一个明确的用人需求,往往会编辑成一些招聘发布到各大招聘网站上,等待简历纷至沓来。一般来讲,企业的用人需求是清晰的,比如招C++后台程序员,程序员的职责就很明确,就是开发后台程序,相应的我们会有一些技能要求,比如对Linux要熟悉,能够熟练掌握各种命令,熟悉TCP/IP,能够随手写出能够运行的面试小程序。企业的建设目标指导职位需求,而职位需求指导技能要求。当然有一些综合能力强的人,还会有一些加分项吸引我们,因为据我们过往的经验,具有这些加分项的人一般也更加具备相应的技能,也能谈得来,比如有github项目或者个人技术博客的,说明动手能力强,有相应项目经验。

招聘从明确用人需求就开始了,这次我们招聘的是设计师,拉钩上的简历非常多,如果这个职位薪资较高或公司足够吸引力,那么将会有很多人投递,那么这里就会充斥着大量的水平参差不齐的被试者,首先要通过筛选简历来过滤掉明确不会要的人。面试官的心态是尽快过滤掉不符合条件的人,但是也不想放过真正优秀的,企业还是很担心失去最佳人选的,所以看简历还是很认真。

筛选简历

许多招聘网站有自动筛选工具,按照条件筛选人员,作为面试官,面对几十份上百份看起来都一样的简历,首先会按照自己的重点来筛选,比如我看的是学历和年龄,由于很多企业的用人要求是本科及以上,对于学历不够高的会先放一放,然后再看看年龄,对于互联网公司,年龄太高还在谋求较低职位,或担心其不能够适应初创公司里全都是九零后小青年,所以这种也先放一放,对于那种简历写的漂亮的,面试官会多加注意,往往优秀的人对于自己的简历会维护的更好,看着整洁大方,重点突出。如果这个被试者前面的条件都符合了,技能点和项目经验都足够,往往会通过初筛。往往一些学历不够好的,但是项目经验很有分量的,或者是学历特别好的(211,985高校本科及以上)的都能够得到面试机会。

很多人会说,不给大专的机会,这不是学历歧视吗?从用人的角度来思考,就是希望尽量降低用人的风险,学历低有什么不好?说明至少学生时期没能好好学习或智商不够高,另外一个担心是学历低的同学思维方式及沟通能力不足,经过观察很多大专的被试者简历都显得啰嗦混乱,面试过程中沟通起来也会更加费劲一点。不过也有看到很多大专学历的同学,自己真的是因为兴趣去热爱设计,所以作品看起来非常好,看得面试官很高兴,又或者自己努力自考读了一个本科,至少是将这么一个缺点给克服下去了,所以通过筛选的也有一部分是学历不够高的同学。

面试

面试过程主要是了解个人情况,考察技能掌握程度,及看是否沟通顺畅。面试的过程比较自由发挥,但是我总结了一下,有几点还是必须试的。因为不同的岗位有不同的技能要求,所以对不同的岗位的技术问题我们就不详细讲了,主要抽出几点通用的东西。

自我介绍

这是了解面试者的基本情况的方法,当然可能面试官已经通过简历了解到了一些,所以考察的主要是口头表达能力,以及观察其谈吐是否逻辑通畅,面试官也可以利用这个空档看看简历,准备下一个问题。如果表达流畅,那么就可以从刚刚的介绍切入,原来你是从xx毕业的啊,那里balabala,先缓解面试者的紧张情绪,接下来就好问一些深入的问题了。

当场笔试

考察技能最好的就是当场笔试,如果是考察编码能力,当场写个小程序,他的所有操作过程你都能看到,如果你和他一起讨论,还可以缓解紧张。最近面试的一个从腾讯出来的后台开发,就让他当场上机写一个小程序出来,他一开始有点紧张,但是经过提示,很快就得到正确的程序了,并且在过程中和我有交流,并且在他编程的过程中,我能看到他使用Linux的命令行较为熟练,使用编译工具和Vim比较熟练,那么我基本就能判断他是一个合格的后台程序员了。如果说面试的是设计师,怎么办,那就让Ta尝试绘制一个图标或者将详细的方案阐释出来,并紧追着问一些细节,以确认Ta却是是干过这个事,并且干得很好。最近面试的一个被试同学简历上写着英语很好,我当场让Ta翻译一段文字,结果翻不出来,Ta自己都觉得尴尬,所以这样就能够试验出一个人的真正水平是不是如同简历里写的那样。这里也给我们提供了一个启示——没把握的东西不要写到简历上,降低面试官的预期,这样才能给出惊喜,就算没有答上也无关紧要,就像谁会苛求一个英语专业的人会编程呢,不会才是正常的。

自我评价

经过上面比较深入地考察专业技能,面试者的水平也基本摸透了,接下来的几个问题可以轻松一点,问问平时上什么网站,有什么兴趣爱好之类的,考察视野是否开阔,聊得好可以多聊一下,顺便问问『你的核心竞争力是什么?』,这个问题可以暴露出面试者到底自信不自信,如果是自信的面试者可能会用一个有层次的回答来收尾,最好就是从元能力-自身经历-专业技能依次推导,或者反过来推导,一般的面试者会说些专业之类的别的躲避过去。至今我还没有见到哪个面试者对于这个问题有很好的回答,当然这个问题面试官一般也不会太苛求要答成什么样子。这个问题问完估计就问问还有什么问题要问,之后就例行结束面试。

从面试官的角度,会得到许多对于面试者的启发,那些面试官所看重的东西,就是面试者要注重学习的。当然这个世界还有一种运气的问题,不排除有的技术不过关的人可能聊得很好通过了面试,但是企业一般会通过复试来规避这样的风险,我也觉得复试是很有必要的,确保自己招的人是符合要求、与团队契合的,能够降低风险,也让整个团队高效、融洽、轻松。

by bibodeng 2016-10-14

使用Nginx+uWSGI+Supervisor部署Flask应用

发表于 2016-10-12   |   阅读次数

使用Nginx+uWSGI+Supervisor部署Flask应用

前言

许多团队使用的是Nginx和uWSGI的方式来部署Python网页应用,ViaBTC的站点采用的是简单高性能的Flask开发的,那么究竟怎么才能让Flask开发的网站能够正确部署到Nginx中呢,刚好,bibo接手了网站应用的开发,刚好通过这次实验介绍一下这个步骤。

先简单介绍一下这些工具:

  • Nginx ——高效的web服务器
  • uWSGI ——WebServiceGatewayInterface的一种实现,是Web服务器到Python应用的桥梁
  • Supervisor ——一款强大的进程监控管理工具,它能够启动uWSGI

准备和安装

我们将会以Flaskr-demo 为例子,将它作为一个Web服务启动起来,让它的页面能够被用户访问。假设你已经有了服务器,首先要安装Python环境,我们推荐Virtual env,它能够提供一个虚拟的Python运行环境,要使用什么Python包都得在venv模式下安装,对应这个Python运行版本,组件一套软件包,从而保证在这个环境下应用可以独立运行,避免了在全局安装使用同一个Python版本,那样会造成混乱和各软件包不兼容。

// 安装准备工具
sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install virtualenv

// 安装python
sudo add-apt-repository ppa:nginx/stable
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential python python-dev

接下来安装并启动Nginx:

sudo apt-get install nginx
sudo service nginx start

nginx安装好之后,我们可以通过访问web服务器来验证是否安装正确,这个时候应该能看到Nginx的欢迎页:

curl http://localhost

实现功能

假如你的代码已经实现好了,只要拷贝到 /var/www/ 目录下并修改目录权限,程序对脚本要有读和执行权限,如果没有这个目录,通过mkdir命令建立一个即可,这里我们从0开始实现Flasker这个例子,然后将它启动起来。Nginx为我们提供了一个托管静态页面的站点,但是它不能直接运行Python代码,所以应由uWSGI和Nginx配合共同呈现动态的页面。

可以参考Flask的文档逐步实现,实现好之后的目录如下,其中flaskr.py是整个程序的代码,我们可以尝试运行一下这个demo,将会提示已经在 http://127.0.0.1:5000 调试运行了,访问这个网址将会呈现出它的页面。但是这并不是我们要的,我们需要让它运行在Web服务器上,并提供给所有人访问。

    |---------flaskr/---------
|--static/
|--templates/
|--__init__.py
|--flaskr.py
|--schema.sql

运行调试模式python flaskr.py将显示下面提示:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!

##配置Nginx

下面使用uWSGI和Nginx配合,让uWSGI执行Python程序,并将结果输出到Nginx服务器,它们将通过一个socket进行通信,首先,要安装uWSGI:

sudo pip install uwsgi

我们在项目目录下建立一个config目录,并新建一个配置文件 demo_nginx.conf 来配置nginx,当然我们

    server {
    listen 80;
    server_name demo.viabtc.com;

    location /static/ {
        alias /var/www/flask_demo/app/static/;
    }  

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/demo.uwsgi.sock;
        uwsgi_param UWSGI_PYHOME /var/www/flask_demo/venv;
        uwsgi_param UWSGI_CHDIR /var/www/flask_demo;
        uwsgi_param UWSGI_SCRIPT run:app;
        uwsgi_read_timeout 100;
    }  
}

以上配置表示服务器监听 demo.viabtc.com:80,并配置了 static 目录的位置,当然你也可以配置其它自己添加的目录,还可以看到uwsgi和Nginx是通过 /tmp/demo.uwsgi.sock 这个文件来进行转接的,也即当Nginx接收到请求后,将请求传递到 demo.uwsgi.sock,由uwsgi去处理。

此外,由于uWSGI不会像python flaskr.py这样以__main__的名义运行程序,故而我们配置了运行入口是run:app,也即我们定义了一个run模块(即run.py文件),运行的时候app context就是run模块的app。

    #!/usr/bin/python
# -*- coding: utf-8 -*-

'''run.py is simple'''
from flaskr import app as application
app = application           

别忘了脚本都要赋予执行权限:

sudo chmod +x run.py

然后将该demo_nginx.conf配置文件链接到Nginx的配置文件包含目录/etc/nginx/conf.d/下,并重启Nginx查看效果:

sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo service nginx restart

下面在客户端机器中配置你的访问hosts,例如我的是:

112.74.164.155 bibo.viabtc.com

访问服务器站点 demo.viabtc.com,会发现报了502 BadGateway错误,那是因为我们没有配置uWSGI,所以在Gateway这个环节出错了,我们下面直接通过Supervisor来启动uWSGI解决这个问题。

配置Supervisor并运行uWSGI

首先,要安装Supervisor,安装Supervisord(服务端),会自动配套安装Supervisorctl(客户端),Supervisord负责控制那些需要托管的进程。

sudo apt-get install supervisor

安装完成后,我们可以查看Supervisor的配置文件 /etc/supervisor/supervisor.conf,一般无需改动全局配置文件,但是对于supervisor的包含配置文件,也即运行时需要查找的应用配置,放在/etc/supervisor/conf.d,和Nginx是类似的,我们也需要在这个目录下添加站点的配置文件,我们同样使用链接的方式。

在项目文件夹的config/目录下新建一个demo-uwsgi.conf文件,内容是app的配置:

    [program:demo_uwsgi]
command         = uwsgi -w run:app --master --processes 4 --socket /tmp/demo.uwsgi.sock --chmod-socket=777
directory       = /var/www/flask_demo
autostart       = true
autorestart     = true
stdout_logfile  = /var/log/uwsgi.demo_web.log
redirect_stderr = true
stopsignal      = INT 

其实Supervisord就是运行了这样一个命令将uWSGI启动起来了,并且指定了日志输出等配置,我们通过软链接方式将这个配置文件放到/etc/supervisor/conf.d,然后启动Supervisord,并启动我们站点:

sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo service supervisord start
sudo supervisorctl start demo_uwsgi

这个时候项目的整体目录看起来像下面这样:

    |---------flaskr/---------
|--config/
|--|--demo_nginx.conf
|--|--demo_uwsgi.conf
|--static/
|--templates/
|--flaskr.py
|--run.py
|--schema.sql
|--venv/

这个时候再次访问demo.viabtc.com应该就可以正常访问了,不再出现502错误,以后将要新建一个站点的时候我们可以按照上面的方法依法炮制,只需要重新定义一套**_nginx.conf 和 **_uwsgi.conf 并放到对应的地方即可。如果碰到问题,不妨先查看一下Nginx和Supervisor及uWSGI的日志,日志地址就是上面配置文件中我们定义的。

bibodeng 2016-10-12

连接一切的超级总线

发表于 2016-07-02   |   阅读次数

最近有个很疯狂的想法,是否有可能实现一个平台,去连接整个世界?


且听我怎么说,我说的这个平台,他能够连接的,是设备与设备,设备与App , App 与App ,App与系统等等,最终的目的是让整个世界连接起来(所谓的物联网),包括人。


其实我所说的这种连接的概念在社交网络里面已经存在了,Facebook上,大家可以关注某一个人和另外一个人成为朋友这样你就可以订阅他的所有消息,你一个陌生人可以知道扎克伯格每天在干啥。QQ微信上,组建了一个群群里面某个成员发送了一条消息,大家都能知道这条消息,并根据消息做出自己反应(例如抢红包)。但是,现在App与App之间就像两座孤岛永不相连。仅有的连接只是存在一些开放平台上,例如登录,分享,搜索,支付,而这些领域都逐渐生长得稳固并获得了成功(这意味着那些还没出现或强壮的都是潜力领域)。这里我发现了一个走向未来的规律。如果这个连接发展成为成千上万,那将会有无数家百度,阿里,腾讯规模一样的公司,做着这种连接的工作。然而按部就班的发展速度再也不适合互联网的进化速度,ta需要更加快的生长速度——爆炸。


想象一下,任何一个联网的物体、人、系统,他们成为一个和人一样的存在,它产生信息,其它联网的万物都获取到这个信息。便如太阳升起,有无数人or物实实在在感觉到,并做出反应,或转动花盘吸收阳光,或自然醒来劳作,或啼鸣报晓。这是事物交互应当的方式。信息就在那里,各自去解读吧!


如何做到万事万物的连接呢,除了万物自然的连接,我们为万物打造一个信息的容器,我叫它超级总线。信息在超级总线流动,产生信息的叫发送者,接收信息的叫作订阅者。一个发送者,可以被多个订阅者订阅,自身也可以作为订阅者订阅自身或者他人发送的信息。下面假设几个场景,大家试想一下它是不是自然的,合理的。


1. 太阳落下,屋外的感光探测器发现了光线变暗,发送了一条消息到超级总线,并被广播到了那些订阅了传感器的电灯上,电灯们根据自己的职责被点亮或变色(取决于设置以及自身的能力)。现在的智能家居是怎么做的?是把这些通知到你手机里,然后你自己去App上控制灯打开(好累),要么就是将传感器安装到达和电灯一个电路上,仅仅能够控制一盏。


2. 我有两个App,一个是写日志的“为知笔记”,另外一个是记录我的习惯养成的“种子习惯”,当我写完日志,为知笔记发送了一条消息到超级总线,告知日志写完了。而种子习惯收到消息,在今天任务上记下了我完成了任务。两个App成了相互协作的“人”。这里的协作可以推而广之,可以是任何我们能想到的协作。现在的App大家是怎么做的?完全隔离,数据不共享,我跑了步还得去todo清单里面打个勾(好累)。


3. 更geek一点,你要假设一个App和一套对应的服务站点。两者之间需要通信,除此之外,你还想在用户请求服务器的时候纪录一条日志。假设当用户搜索了一次搜索框,这个请求推送到了超级总线,而业务处理站点和日志站点分别订阅了来自App的请求,这个请求被分发到两个站点(与现在互联网的模式不同),两个站点做不同响应,一个返回了搜索到的数据,另外一个记录了一条日志,返回了一个OK。这两个响应同样以消息通知的方式返回到超级总线,App订阅了来自业务处理站点的消息,收到响应后将结果展示给用户。而App不关心日志记的结果如何,可以不订阅日志站点的消息,或者收到了什么也不做。这样既解耦了业务和日志,一切都这么优雅,完美基于现有的互联网。


上面只是超级总线的几种比较典型的应用,但是这个连接将会极大丰富,每个系统面向的可能都是上亿的用户(人或者机),只需要定义一套自己消息的API和消息规则(HTTP的天然方式),就能发送消息和接收消息了,当然接收后如何处理则是开发者的职责了。可以说,这样的事情,号称要连接一切的腾讯都未能做到。但是各种各样的平台确是在推进这件事。从印刷术,到电视机,到互联网,都是在一生多,直到无数(一次刻版多次印刷,一次录制无限次播放)。而且,这也是互联网最终的发展方向,它从单机到联网,到Web1.0到Web2.0,我预感到它正在向Web3.0跨越。互联网连接越来越丰富,信息在广泛传播,那么这个巨大的网络,则形成了一个有意识的“天网”,因为我们人类被涵盖在里面,这让人又忧又喜。


这样的系统,很危险,所以必须考虑各种安全机制来保证通信安全,是否互相信赖,以及如何退订,防范递归,如何在危及人类之前发现并处理。都需要完整的方案,而现有的技术和推广,推进这件事情必须耗费大量的时间和资源,而且这里还涉及人性的斗争。但这是值得的,因为生产力极大提高,你不需要去做那些中间传递的琐碎工作。我们可以从小处试水,例如场景3,两个互相协作的系统的demo还是能搞出来的。

by bibodeng 2016-06-28

转载请注明出处,欢迎转发

AssertUtil断言式异常处理

发表于 2016-06-08   |   阅读次数

#AssertUtil断言式异常处理

在程序开发中,异常处理是最常见的,但是经过了两年的工作,我才慢慢理解到异常处理应该怎么做,在学校的时候并没有做异常处理的习惯和成熟的方法。加入弹性福利的开发以来,编写了许多关于异常处理的代码,对于异常处理,怎么样处理得优雅得体,对于一个项目来说,还是比较重要的,尤其是在运营阶段,如果不断被异常轰炸,是会令人崩溃的。

##异常的结构
在阅读一些计算机编程经典的时候,常常被教导写代码要用try...catch...finally格式,try里面放可能出错的代码,catch后面应该多跟具体的异常,里面放异常的处理,尽量不要过长,避免在异常处理的时候出现异常,finally里面放结尾工作,例如关闭文件、清理资源等。但是平时写代码的时候,常常有一个疑问,那就是何时处理异常,何时抛出异常?其实,这个答案,我至今还不知道。目前能给出的答案是,我们在容易出错的地方捕捉异常,在捕捉异常的时候,我们尽量在同一个层次去处理异常,如果本层次处理不了,那就抛出到更高的层次做处理。对于抛出异常的情况,我们更多是定义业务上的异常(程序本身可能并没有抛出异常),或者将异常包装以后抛出给上个层次处理,这样子异常的层次就比较整齐。实际上throw和GOTO一样,都是一种破坏结构性的代码跳跃,使得我们的整洁性被破坏。

try{
    // 可能出错或者希望不要出错的逻辑代码
}
catch(MyException1 myException1){
    // 处理第一种情况异常
}
catch(Exception exception){
    // 处理剩余情况异常
}
finally{
    // 扫尾清理
}  

以上就是异常处理的经典结构,try包围了想要确保正确的代码,第一个catch只处理第一种异常情况,第二种处理剩余异常情况,二者只有一个会执行,在逻辑复杂的情况下,有可能发生多种异常情况,而不同情况的处理方法并不相同,所以尽量定义详细的异常,方便catch到之后处理。如果以大而全的Exception来替代多个详细catch的话,如果要处理详细的异常,就得这样写:

try{
    // 可能出错或者希望不要出错的逻辑代码
}
catch(Exception exception){
    // 处理所有异常情况
    if (exception is MyException1){
        // 处理异常情况1
    }
    else if (exception is MyException2){
        // 处理异常情况2
    }
    // 处理其它情况异常
}
finally{
    // 扫尾清理
}  

这样写有个弊端,嵌套的层次多了,还不如直接catch来得简洁直接,另外一个,当你修改了内部抛出的异常类型,你不能直观发现到底这种异常被catch处理了没有,还得看看if语句里面的条件。

##处理业务异常
目前我们还存在的问题是,每个层次的代码都有可能抛出异常,每个层次的异常又可能有两种不同的处理方法,一种是被catch镇压了,一种就是被throw上报了。我们希望,那些上层逻辑不关心的异常,被catch镇压,但是如果碰到上层关心的异常,务必要上报出来做处理,而且尽量以业务异常的方式抛出,而不是以一个大而泛的Exception概括。

外层的catch能起作用,是因为内层代码抛出了异常,要么就是自己写的代码里throw,要么在所调用的函数中有throw。那么一般是什么情况下才会throw呢?因为throw之后,程序是不会继续按顺序执行的,而是跳出来外部的catch中执行,所以只有出现异常执行不下去的时候,会选择抛出。例如,某个输入有误不是我们要的,或者网络发生错误重试后仍然报错,再比如很常见的该方法未实现,这些情况都是要抛出来给外层处理的。

为了方便,有时候我们也需要定义自己的业务异常,以和其它异常区分开来,碰到这种业务异常,处理成为返回失败,然后将异常消息返回。

##使用断言式异常
断言,是使用assert 条件,不符合条件后执行语句的方式,通过当前运行中的变量做一些推断,例如运行到一个赋值a=b;语句后,我推断a == b,如果不成立,那程序有问题,不能执行下去。当然现实生活中的例子更加复杂一点,例如对支付的金额是这样断言的 assert cost > 0 , throw BizException("支付金额须大于0")。通过这样的断言式异常,我们可以方便地对数据和当前的变量条件做判断,从而确保不正确的时候不会继续运行下去。于是我们实现了一个模块,专门判断各种条件,用IsTrue, IsFalse, IsInEnum等方式来判断各种我们想要判断的条件,这样通过校验,基本上能够让程序准确无误地运行下去,一旦发生错误,这个异常必然被捕获,报告到开发人员那里。

public sealed class Validator
{
    private IList<string> exceptionMessageList = new List<string>();
    private Validator() { }
    /// <summary>         /// 获取实例
    /// </summary>         public static Validator Instance { get { return new Validator(); } }
    #region 抛出断言异常
    /// <summary>         /// 抛出异常
    /// </summary>         /// <param name="msg" />异常消息
    public Validator Fault(string msg)
    {
        exceptionMessageList.Add(msg);
        return this;
    }
    /// <summary>         /// 检验完成
    /// </summary>         public void Done()
    {
        if (exceptionMessageList.Count &gt; 0)
        {
            throw new AssertException(string.Join("~", exceptionMessageList.ToArray()), exceptionMessageList);
        }
    }
    #endregion
    /// <summary>         /// 断言对象不为空
    /// </summary>         /// <typeparam name="T">对象类型</typeparam>         /// <param name="o" />对象
    /// <param name="message" />异常提示信息
    public Validator IsNotNull<t>(T o, string message = "传入参数不能为空!")
    {
        if (o == null)
            Fault(message);
        return this;
    }
} 

Validator实现了一个类,包含了一个异常消息列表,一个Fault判断函数,将错误的消息加入到异常消息列表中。 该Validator内可以实现自己的判断逻辑,如IsNotNull,判断该传入的对象o是否为null,如果是null,则将异常信息放入异常消息列表。

/// <summary>     /// 断言对象不为空
/// </summary>     /// <typeparam name="T">对象类型</typeparam>     /// <param name="o" />对象
/// <param name="message" />异常提示信息
public static void IsNotNull<t>(T o, string message = "传入参数不能为空!")
{
    Validator.Instance.IsNotNull(o, message).Done();
}  

实际应用时,使用一个static类AssertUtil实现若干类似static IsNotNull的方法,使用时,直接在代码中加入断言式判断如果碰到异常情况,将会抛出一个AssertException,外层代码对该AssertException进行处理即可。

JsonResultEntity result = new JsonResultEntity();
try
{
    AssertUtil.IsNotNull(obj, "参数不能为空!");
    //AssertUtil.IsTrue(obj.PointValue &gt; 0, @"积分数必须大于0");
    // ...

}
catch (AssertException ex)
{
    result.resultMsg = ex.Message;
}
catch (Exception ex){
    MessageCenter.Instance.SendAppWebErrorMail(CurrStaff.FullName, ex.Message, this.Request.Url.ToString(), ex);
}
return result; 

##结语
异常的处理方法,如果是业务类的异常(如业务不允许非正式员工下单)或输入类的异常(用户支付了一个-1元),我们一般是记录一个日志,然后封装成为消息返回给用户。而如果是程序级的错误,如除于0,类型转换错误了,服务器500了,我们都会通过日志或者邮件的方式通知管理员,这里发生异常了,程序有问题哦。当然,异常消息的通报频次,通报级别有赖于自己实现程序控制。最终,监控的目的是为了正确,做异常处理能够做到,只要发生异常,就能定位所在地方,只要推送了异常消息,就必定是我们关心的(有用的异常),那么程序将会运行得很健壮,因为各种情况你都考虑到了,并有严密的通报机制,所以有错误也很快被纠正。

做了异常处理,你的程序不会以崩溃的方式退出,即使有错,也是优雅地退出,而优雅的AssertUtil断言式异常,则让异常处理更加优雅。

by bibodeng 2016-6-6

用React-Native开发一款App

发表于 2016-06-05   |   阅读次数

React-Native开发一款App

想做一款App

我有一个用emlog的博客,我常常在上面发点碎语,吐槽一下生活,偶尔发发段子。虽然只有几个比较要好的哥们看,但是我还是觉得不够便捷,每次都需要打开电脑上的浏览器,访问我的博客,才能看到里面的文字。于是,我打算搞一个App出来,说搞就搞,于是在emlog的基础上,将一个php程序改写成API的方式,输出Json格式的数据。只用了一个转换函数:

  1. // 返回json编码的数据
  2. function response_json($status, $status_message, $data)
  3. {
  4. header("HTTP/1.1 $status $status_message");
  5. $response['status'] = $status;
  6. $response['status_message'] = $status_message;
  7. $response['data'] = $data;
  8. $json_response = json_encode($response);
  9. echo $json_response;
  10. }

使用起来就像这个样子:

  1. function getTwitter()
  2. {
  3. $tid = isset($_GET['tid']) ? intval($_GET['tid']) : 1;
  4. $Twitter_Model = new Twitter_Model();
  5. $tw = $Twitter_Model->getTweet($tid);
  6. }

当然,服务端为了完成所有的功能,还需要自己动手编写逻辑代码,由于主要是数据库的增删改查,这里就按下不提了。

Why React-Native

为啥用React-Native呢?因为Object-C和Swift我都不会。对于曾经的前端工程师而言,没有什么能比JavaScript更熟悉了,JavaScript算的上是一款相对应用广泛的语言,前端后端终端都能通吃。在iOS及Android上开发App,除了以前的Native实现方式之外,有了一种全新的模式可供选择,这就是React-Native,一种基于JavaScript的Facebook推出的框架。它通过独特的虚拟DOM,组件化与Native组件进行一层转接,这样就能兼顾开发效率和运行效率了,最终效果和编写Native代码写出来的程序没有很大区别。而这种组件化的方式,能够尽可能地让你复用组件,而不是自己开发。

React-Native开发

使用React-Native进行开发,最好的文档就是Facebook的官方文档了,看了它的工作方式和一些基本技术之后,才具备实现一个App的基础。我是通过一本图灵社区的书《React Native入门与实践》来入门的,这本书对于入门者更加友好一点,也有详尽的例子供练习。

了解基础布局

要实现App中的布局,就必须要了解JSX语法和React-Native中的flex布局,JSX是一种类似于HTML语法标记的语言,可以在JavaScript中嵌入这类标记,从而实现元素布局和嵌套。一个很简单的JSX例子就是直接返回一个标签,一个React-Native组件有好几个组成部分,整个组件被一个Class定义,内部含有初始化状态的getInitialState, 组件加载完成后的componentDidMount及组件渲染函数Render。

  1. import React, {
  2. AppRegistry,
  3. Component,
  4. StyleSheet,
  5. Text,
  6. View
  7. } from 'react-native';
  8. class MonkeySay extends Component {
  9. // 组件渲染
  10. render() {
  11. return (
  12. <View style={styles.container}>
  13. <Text style={styles.welcome}>
  14. Welcome to React Native!
  15. </Text>
  16. <Text style={styles.instructions}>
  17. To get started, edit index.android.js
  18. </Text>
  19. <Text style={styles.instructions}>
  20. Shake or press menu button for dev menu
  21. </Text>
  22. </View>
  23. );
  24. }
  25. }

对于布局,它和CSS布局有相似之处,也有不同之处。例如color和width,height这些常见的布局是类似的,但是flex布局和CSS的绝对相对布局有很大不一样,那就是flex布局尽可能地让box根据一些规则自动排列,例如下面的一些属性,用于控制box的呈现:

  • alignItems 在主轴上的对齐方式
  • alignSelf 在次轴上的对齐方式
  • flex 用于控制box的比例
  • flexDirection 用于选择主轴,默认是Column
  • flexWrap 元素包围方式,空白or紧凑
  • justifyContent 次轴上的对齐模式

一个很经典的练习是,如何在屏幕中布局出一个回字型的box,并且能够让里面的字体都居中展示。 
完成了以上的练习,还需要了解一些在组件中传递数据的基础知识,在组件中,可以通过属性传递值,即在标签Render的时候,传递一个属性值 如<MyList tweet={tweets} > </MyList>,这样就可以将tweets变量传递到MyList组件中,引用的方式是this.props.tweet,另外一种传递方式是,通过在具有passProps属性的组件(如Navigator)中传递到组件中。 
还有一个比较重要的概念是组件的this.state,在该组件创建的时候,便会执行一个初始化函数getInitialState,设置组件初始的state值,一般情况下this.state的函数变量的值发生变化的时候,Render函数将进行一次重绘,是否重绘由另外一个自定义函数决定。

实现核心逻辑

「猿说」要做的很简单,就是把我博客上的碎语呈现到App列表中来,如果可以还能查看详情或者评论。那么我们需要使用Ajax技术来获取在服务器上的数据,然后用ListView来实现一个列表,容纳碎语的数据,并设计其布局,让其完美呈现出来。

首先,按照默认程序的结构,新建一个组件单独存储到一个js文件中,我们暂且就叫它TweetList.js吧,下面一步步来实现其功能。

  1. 'use strict';
  2. var React = require('react-native');
  3. var TweetDetail = require('./TweetDetail'); // 详情页面
  4. var API = require('./ServerAPI'); // API地址
  5. var Authorize = require('./Authorize'); // API授权相关
  6. var {
  7. Text,
  8. View,
  9. StyleSheet,
  10. Image,
  11. Navigator,
  12. TouchableOpacity,
  13. ActivityIndicatorIOS,
  14. TabBarIOS,
  15. ListView,
  16. AppState,
  17. RefreshControl,
  18. Platform,
  19. Component,
  20. AlertIOS,
  21. } = React;
  22. var ds = new ListView.DataSource({
  23. rowHasChanged: (row1, row2) => row1 !== row2,
  24. sectionHeaderHasChanged: (s1, s2) => s1 !== s2
  25. });
  26. var TweetsList = React.createClass({
  27. getInitialState: function() {
  28. return {
  29. dataSource: [],
  30. loaded: false,
  31. message: '', // 报错信息
  32. maxPage: 1,
  33. refreshing: true,
  34. screen_name: '游客',
  35. };
  36. },
  37. // 加载
  38. componentDidMount: function() {
  39. // 尚未加载过,则请求初始数据
  40. if(!this.state.loaded) {
  41. this._loadinitData(1);
  42. }
  43. },
  44. // 获取数据
  45. getData: async function(pos, pageIndex) {
  46. if(!this.state.dataSource) {
  47. var pageIndex = 1;
  48. }
  49. var url = API.getTwitters+ pageIndex;
  50. var nonce = Authorize.makeNonce();
  51. fetch(url, {
  52. method: 'GET',
  53. headers: {
  54. 'Accept': 'application/json',
  55. 'appId': Authorize.appId,
  56. 'nonce': nonce,
  57. 'sign': Authorize.makeSign(Authorize.appId, Authorize.appKey, nonce), // API做了签名处理
  58. }
  59. })
  60. .then((response) => response.json())
  61. .then((responseData) => {
  62. if(responseData.status == 200) {
  63. if(this.state.dataSource == null
  64. || this.state.dataSource.length < 1) { // 首次加载
  65. this.setState({
  66. dataSource: [responseData.data], // 将数据绑定到state中
  67. loaded: true,
  68. maxPage: 1,
  69. });
  70. } else {
  71. var arr = this.state.dataSource;
  72. if(pos=="top")
  73. {
  74. arr.unshift(responseData.data);
  75. }
  76. else
  77. {
  78. arr.push(responseData.data);
  79. }
  80. this.setState({
  81. dataSource: arr,
  82. loaded: true,
  83. maxPage: this.state.maxPage < pageIndex ? pageIndex : this.state.maxPage,
  84. });
  85. }
  86. } else {
  87. AlertIOS.alert('提示','暂无最新,请稍等片刻~');
  88. }
  89. }
  90. ).catch(error=>{
  91. })
  92. .done();
  93. },
  94. // 异步加载数据
  95. _loadinitData :async function() {
  96. await this.getData("bottom",1);
  97. },
  98. // 点击单条查看详情
  99. rowPressed: function(tid) { // 点击进入详情
  100. this.props.navigator.push({
  101. title: "详情",
  102. component: TweetDetail,
  103. passProps: {tid: tid, screen_name: this.state.screen_name} // 使用了passProps
  104. });
  105. },
  106. // 渲染整个列表,列表带refreshControl
  107. render: function(){
  108. try
  109. {
  110. //this.refreshState();
  111. if (!this.state.loaded)
  112. {
  113. return this.renderLoading();
  114. }
  115. else
  116. {
  117. return (
  118. <ListView
  119. dataSource={ds.cloneWithRowsAndSections(this.state.dataSource)}
  120. renderRow={this._renderRow}
  121. style={styles.listView, {marginTop: (Platform.OS === 'ios')? 64:48}}
  122. initialListSize={20}
  123. pageSize={10}
  124. scrollRenderAheadDistance={50}
  125. removeClippedSubviews={true}
  126. minPulldownDistance={30} // 最新下拉长度
  127. onEndReached={this.onEndReached}
  128. onEndReachedThreshold={100}
  129. renderFooter={this.renderFooter}
  130. refreshControl={
  131. <RefreshControl
  132. ref="listRefreshControl"
  133. refreshing={!this.state.loaded}
  134. onRefresh={this._reloadLists}
  135. tintColor= "#ccc"
  136. title="正在拉取数据..."
  137. />
  138. }
  139. />);
  140. }
  141. }
  142. catch(err)
  143. {
  144. AlertIOS.alert("提示",err);
  145. }
  146. }
  147. // 渲染单条
  148. _renderRow: function(rowData, sectionID, rowID) {
  149. try
  150. {
  151. if(rowData!=null)
  152. {
  153. return (
  154. <TouchableOpacity
  155. activeOpacity={0.4}
  156. onPress={() => this.rowPressed(rowData.id)}
  157. >
  158. <View>
  159. <View style={styles.rowContainer}>
  160. <Image style={styles.thumbnail} source={require('image!writer')} />
  161. <View style={styles.textContainer}>
  162. <Text style={styles.name}>bibo-果冻</Text>
  163. <Text style={styles.tContent}>{rowData.resource.content}</Text>
  164. {(() => {
  165. if(rowData.resource.img!=null&&rowData.resource.img.length>0)
  166. {
  167. return (<View style={styles.imgContainer}>
  168. <Image style={styles.tpic} source={{uri: API.imgBaseUrl + rowData.resource.img}} />
  169. </View>);
  170. }
  171. else
  172. {
  173. return (<Text></Text>);
  174. }
  175. })()}
  176. <View style={styles.row}>
  177. <Text style={{flex:1}}>{rowData.resource.date}</Text>
  178. <Text style={styles.commNum}> 评论({rowData.resource.replynum})</Text>
  179. </View>
  180. </View>
  181. </View>
  182. <View style={styles.separator}/>
  183. </View>
  184. </TouchableOpacity>
  185. );
  186. }
  187. else
  188. {
  189. return (<Text>None</Text>);
  190. }
  191. }
  192. catch(err)
  193. {
  194. AlertIOS.alert("提示",err);
  195. }
  196. },
  197. // 渲染加载标志
  198. renderLoading: function()
  199. {
  200. return (<ActivityIndicatorIOS
  201. hidden='false'
  202. size='large'/> );
  203. },
  204. // 下拉重新渲染,获取最新数据
  205. _reloadLists: function() {
  206. this.setState({loaded: false});
  207. setTimeout(() => {
  208. this.setState({dataSource: []});
  209. this.getData("top", 1); // 从第一页获取
  210. this.setState({loaded: true});
  211. }, 500);
  212. },
  213. });

App的总体逻辑就是,加载之后,先通过API获取初始的数据并渲染,总体程序如上。除了上面所说的React-Native组件中的几个基础函数,要实现一个真正可以用的List,还需要添加很多业务逻辑。渲染方面,使用state中的数据进行渲染,渲染一个碎语列表,可以看成是渲染多个单条的碎语,故而可以把渲染单条的代码抽出来,即使用renderRow方法,甚至是将单条的碎语独立封装为一个组件,即在标签内部嵌入子标签。此外最核心的,莫过于从服务器端获取数据,我们使用一个fetch函数进行http的请求,从url指定的API中获取数据。fetch结构如下:

  1. fetch(url, {
  2. method: 'GET',
  3. headers: {
  4. 'Accept': 'application/json',
  5. 'appId': Authorize.appId,
  6. 'nonce': nonce,
  7. 'sign': Authorize.makeSign(Authorize.appId, Authorize.appKey, nonce), // API做了签名处理
  8. }
  9. })
  10. .then((response) => response.json())
  11. .then((responseData) => {
  12. // TODO:自定义逻辑
  13. }
  14. ).catch(error=>{
  15. // 建议做异常处理
  16. })
  17. .done();

fetch请求发出后,将会返回一个promise对象,你可以对它进行.then().then.()的链处理。很经典的处理办法就是讲response中的数据json化,然后在下面的自定义逻辑中,将json数据赋予到state中。 
数据加载到本地之后,对数据的渲染还需要添加布局属性,给标签内的style属性赋值,即可让该标签拥有布局属性了。

  1. var styles = StyleSheet.create({
  2. container: {
  3. flex: 1,
  4. flexDirection: 'row',
  5. justifyContent: 'center',
  6. alignItems: 'center',
  7. backgroundColor: '#F5FCFF',
  8. },
  9. listView: {
  10. paddingTop: 60,
  11. backgroundColor: '#EDEDED',
  12. },
  13. });

使用StyleSheet.create接口即可创建一组布局描述类,在使用的时候,即可直接引用styles.listView来给标签的style属性赋值:

  1. return (
  2. <ListView
  3. dataSource={ds.cloneWithRowsAndSections(this.state.dataSource)}
  4. renderRow={this._renderRow}
  5. style={styles.listView, {marginTop: (Platform.OS === 'ios')? 64:48}}
  6. initialListSize={20}
  7. pageSize={10}
  8. />);

sytle可以通过两种方式添加描述,一种是赋值,一种是内嵌,直接在标签里定义style={{marginTop: 64}},即可指定组件距离顶部位置了,两种方法是等效的,相当于css中独立样式文件和内嵌样式。 
组件实现后的最终效果应该是这样的。 
猿说界面

App打包和上架

经过一番开发之后,如果App已经完成了,那么就可以提交上架了。上架的过程包括 申请证书、申请AppId、打包、上传包和提交审核。申请证书和申请AppId之类的有许多文章可供参考,例如这篇 App上架流程 ,里面从申请开发者账号到下载各种证书都已经有详细的步骤。

我们这里主要说一下打包,通过XCode进行编译打包,需要将App编译输出调整至Generic iOS Device,然后将React-Native调试模式(调试模式时代码在电脑的服务上)的代码切换成从编译的代码包中获取,即从OPTION1切换到OPTION2。

  1. /**
  2. * Loading JavaScript code - uncomment the one you want.
  3. *
  4. * OPTION 1
  5. * Load from development server. Start the server from the repository root:
  6. *
  7. * $ npm start
  8. *
  9. * To run on device, change `localhost` to the IP address of your computer
  10. * (you can get this by typing `ifconfig` into the terminal and selecting the
  11. * `inet` value under `en0:`) and make sure your computer and iOS device are
  12. * on the same Wi-Fi network.
  13. */
  14. //jsCodeLocation = [NSURL URLWithString:@"http://127.0.0.1:8081/index.ios.bundle?platform=ios&dev=false"];
  15. /**
  16. * OPTION 2
  17. * Load from pre-bundled file on disk. The static bundle is automatically
  18. * generated by the "Bundle React Native code and images" build step when
  19. * running the project on an actual device or running the project on the
  20. * simulator in the "Release" build configuration.
  21. */
  22. jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

然后从Xcode-》product-》Archive中进行打包,打包完成可以上传到AppStore上,但是注意,项目配置中的Version+build不能已经存在,否则上传会失败。上传完成后,在苹果developer后台即可看到上传的包了,填写相关信息如截图、评级、广告标识等,即可提交审核了,苹果一般在一周时间内审核,然后告知结果。注意,广告标识等一些东西,有即勾选有,没有即勾选没有,否则也能被核查出来,判为不过。

如果幸运,你的App通过审核了,那么上架后即可在AppStore上看到了,因为存在缓存,一般在上架后半小时才能查到。下图是猿说上线后在AppStore中搜索的结果。 
AppStore搜索猿说

一次事故说编程的不良习惯

发表于 2016-01-12   |   阅读次数
今天发布的时候又出了一些状况,我听到之后吃饭都不香了。在开发的过程中,我修改过测试环境数据库结构,但是没有文档化成SQL语句,导致测试环境和正式环境不一样,调试发现测试环境没有问题,但到了正式环境运行起来就有问题了。这是一个行为习惯的问题,是的,我从小到大都是一个比较丢三落四的人,我习惯试错然后正确改正。但是在严格要求要与预期输出结果匹配的条件下,我常常会犯一些低级错误。这种错误非常不应该,却又非常地消耗时间。同事说让我弄一个正式库下来,一个一个跑,然后看有什么问题,结果因为时间赶,我没听。结果应验了墨菲定理,可能出错的地方真的就出错了,险些又搞出大错。我非常恐惧去比对正式环境和测试环境或者开发环境的不同,因为我心里最偷懒的想法是——它们应该都是相同的,对,是应该,结果它往往就是不同。应该是从根本上,我就应当把开发和发布用不同的标准看待,并根除这种编程的习惯。

开发,是可以自己调试修改,可以随意,但是需要将所有的变更都有迹可循,并良好地文档化;
发布,是将自己的成果交给同事去发布,应提供完整的,正确的程序和他们所能理解的材料。

这种不良习惯,大多是来自于大学时代的那种宽松的编程环境,自己折腾个东西,由于不用考虑规范,不用考虑安全性的问题,随手开发完了随手发布到外网去了。而现在工作上要求的,是要提供严格可用的程序,有许多规范和安全性的问题约束,要考虑的细节非常多,当数据库的更改多了之后,一个个脚本,根本不知道可能会少了点什么,尤其是SQL Server这样的可视化和脚本皆可运行的数据库,容易手动改完后就忘记脚本化了,要是MySQL之类的还好,你做什么都得用脚本,没得可视化可以依赖,这反而也是对效率的提升,因为一旦出了问题,还要花好多时间去弥补。在我们做正事时,反复使用方法校验我们所做的事情是不是都正确了,是一个毫不为过的事情。而要做到编写有顺序,修改有理由,整理有条理,还需要在工作中整理一些有效的方法。之前我在设计一个数据库的时候,每个表都有一些必要字段,如CreateTime, CreatorID, Enable之类的,我都是一个个去添加,加完了发现,有的拼写少字母了,有的顺序又不对,结果同事Hyman指了一条明路,原来可以直接拷贝并粘贴,完全不费力气,正确性又有保证。所以我们做的一些工作,能提升正确性的,能提升效率的都可以多尝试一下,一般都能找到比较好的方法,将事情做得又快又好,杜绝了一些犯低级错误的机会。

另外一个不良的习惯是当一个系统接近完成之时,最令人惊奇和有成就感的部分建造完成,往往就没有了兴趣。剩下的是一些收尾和边边角角的事情,做起来可能会没有什么成就感。就像woz一样,当最伟大的工作完成之后,那些没有什么意思的工作,他连搭把手都不愿意。很多计算机从业人员应该都会有这种病,都喜欢做创造性的事情,而对那些小事不屑一顾,提不起兴趣。然而正是这些细节,容易导致一件事情的成败。所以,作为一个工程师,还是要有一点工程的精神,将这些小事也做好。而做这些小事,也正是更好地看到自己所做的系统,并非是完美的,也为我们后续的运营,做一个铺垫,因为往往是那些小地方容易出错,错了还不知道是错在哪里,这也符合二八原理,80%的错误都发生在那20%的事情上。我们近期做的一个项目中,一个大块的代码模块,实现的是支付的逻辑,极少出问题,却是一些边角条件的处理语句上,出现比较多问题。一般来说,这些bug,都是源于我们在脑海里面想得不那么清楚的一些bug,从而遗留到了代码中,而需要测试人员用不同的视角去发现。

从事计算机行业的工作,需要耐心,细心,创新,激情,而更加基础的,是良好的习惯。

by bibodeng 2016-1-12

让正确的事情持续发生

发表于 2016-01-09   |   阅读次数

去年,完成了几件大事,从小到大以来,我所梦想的都能成真,并被幸运女神所眷顾,得到亲友匡助,从而逢凶化吉,柳暗花明。让正确的事情持续发生,是产品经理的最高境界,也是作为一个独立的人,能够让自己的生活运行在正轨上所应该具备的能力。

在我的周围,总是有一些非常出色的人,他们总是能够把事情做成,我在探寻着,究竟他们具备怎样的一种品质,能够让正确的事情持续发生?经过我的观察,他们大多和我们常人差不多,但是他们心态非常阳光,有明确的追求,关注事情的过程,关注人的感受,永远精力充沛,充满干劲,懂得自我建设。我确信这些品质也必定是来源于他们背后的生活细节。

随着成长,也带来许多的烦恼,责任更大了,压力也更大了,身体不如从前了,心态也不如以前张牙舞爪的青春时晴天居多。但这些都不是做不到的借口,是应当接受生活本来的模样,它有令你烦恼的一面,也有令你兴奋欢呼的一面,如一年四季,如阴晴风雨。但是,你也拥有了更多能量,更加成熟的思维,更加雄厚的财力,足以让正确的事情持续发生。这种能力来自于处理自己和自己的关系——自持(独自一个人待着,都能好好地),需要你能够控制自己,又能启发自己。我们常常不知道为什么而过得很幸福,但是我们却明确知道为什么而失败。暴怒容易伤身败事,而平和能够让人从容应对;怠惰容易百事废弛,发奋却能够积少成多;短视容易莽撞唐突,远虑能够未雨绸缪;胆怯容易变得庸碌,激情却能够让人竭尽全力,无怨无悔!

其实那些有益的事物,得来会很艰辛,细嚼却甘甜,如米饭,而非麻辣烫,虽然酸爽不已,却又伤害身体。来得太快,又易让人容持不了,不懂珍惜,口味太重,后面吃什么都不香了。而让正确的事情持续发生,正是要注重过程,把握行事的习惯,让一个原动力持续驱动你的生活朝更好的方向前进,时间到了自然有所收获。而且,劳作是必要的,这样才能受之而不愧。这个世界,是不确定的,不可知的,但是在稳定的社会中,我们人生的轨迹的近处三五年,却是可见的,将大的梦想划分成小的目标,逐个击破,坚持下去,在机遇来临时,一把抓住,梦想也就不远了。

今年的主题是:让正确的事情持续发生!

by bibodeng 2016-01-09

1234…19
bibodeng

bibodeng

区块链爱好者,互联网从业者

189 日志
20 标签
Github Twitter Weibo
Links
  • IFWallet
  • IFPassword
© 2019 bibodeng
由 Hexo 强力驱动
主题 - NexT.Mist