什么是软件工程师

软件工程师站在主观世界和客观规律之间,理解主观世界和客观规律,尝试使二者达到统一。

程序员、码农,不能很好的解释软件工程师在做什么。
用程序员、码农描述软件工程师,看起来好像软件工程师的主要工作是写代码,实际上,优秀的软件工程师只有 20% - 30% 左右的时间在写代码。
可惜的是,很多软件工程师,以及与软件工程师打交道的人,也认为软件工程师的主要工作是写代码,希望软件工程师花费 100% 的时间在写代码上,认为除此之外的事情,都是在浪费时间。

软件工程师第一件要做的事情,是理解目标。
理解目标这件事与写代码无关,与采用什么编程语言无关,与用什么数据库无关,与采用什么软件架构无关。
大到实现订单系统,小到实现一个列表页面,软件工程师都需要理解这件事,理解目标是什么,理解为什么做这件事能达到目标,理解为什么自己要做这件事,理解是否有更好的方式做这件事。
因为事情越来越复杂,产生的产品经理这个职位,目的是帮助软件工程师理解要做的事情,不能代替软件工程师理解这件事情。

软件工程师要做的另一件事情,是设计。
好的设计,应当是能达到目标的,应当是容易理解的,应当是可扩展的。
太多的人只会 CRUD,妄图通过 CRUD 解决所有问题。认为工程师的大部分职责,只是思考如何落表和取数,以及提供落表和取数的接口。
达到目标的方式,如果只是简单的存取数据表,只是 CRUD 显然没什么问题。流程复杂、操作复杂时,继续只使用简单的 CRUD,就不是一个好的设计了,难以理解,无法扩展。
应对复杂问题的设计,不是回报复杂的设计,而是用容易理解的设计化解复杂度。化解,不是让复杂性消失,而是让复杂性容易理解。复杂性无法消失,但可以容易理解。
现实世界中,交通规则既要提高通行效率,又要保证通行安全。赶过集的人,应该对人多时通行效率低,深有体会。各国的道路交通法,一般紧紧围绕着路权的概念展开:路上画好线,分好路权;判定交通事故责任时,有路权的责任小或者无责。这是交通规则的设计。
计算机世界里,网络通信也非常复杂,硬件不可靠,通信距离非常长,都让网络通信协议的设计非常棘手。无论是 TCP/IP 还是理论中的 7 层模型,都是通过分层解决问题。每一层各司其职,只解决一部分问题。而不是将所有问题集中在一个地方解决。这是网络通信协议的设计。

软件工程师要做的下一件事情,是分工。
现在的软件工程师,已经很难完全掌握解决问题的所有技能,有前端、后端、测试、运维。
分工,就是约定好各自要做什么,如何配合。
为了提高效率,工程师之间一般并行工作,并行工作结束后,大家的工作成果能不能正常配合,这是要在开始并行工作前,商量好的。

最后,才是编码工作。
没有前面几项工作的铺垫,拿到需求就开始写代码,轻则工作效率低,重则代码写出来不能达到目标,甚至造成问题。

哪里需要只会写代码的程序员和码农呢?

Java Annotation 研究

什么是 Annotation

  • 一种可以加到 Java 代码中的语法形式
  • 可以加到指定的 Target 上,Target 类型由 ElementType 定义

为什么需要 Annotation

  • 提醒编译器检查,比如 @Override
  • XML 配置离得太远

Java 预定义的注解

  • Override:编译器用来检查是否重写了父类
  • Deprecated:标记 Target 被废弃,如果使用了 Target,编译器会报 warning
  • SuppressWarnings:支持编译器不要报告指定的 warning
  • Retention:Meta Annotations, 标记 Annotation 的保留时间
    • SOURCE:仅在代码中,被编译器忽略
    • CLASS:保留到编译阶段,不会保留到 Java 虚拟机运行时
    • RUNTIME:保留到 Java 虚拟机运行时
  • Documented:Meta Annotations,标记 Annotation 应当出现在 JavaDoc 中
  • Target:Meta Annotations,标记 Annotation 应当用在哪些类型上,见 ElementType
  • Inherited:Meta Annotations。如果使用到 A 类上,A 类的子类,都会被标记。
  • SafeVarargs:指明使用可变长度范型类型参数的方法和构造器是安全的
  • FunctionalInterface:指明 Interface 是一个 functional interface
  • Repeatable:指明对同一个 Target 可以使用多次注解

Meta Annotations

  • 用于 Annotation 的 Annotation

ElementType:可注解的 Target 枚举

  • TYPE:类,接口(包括 Annotation),枚举
  • FIELD:类的成员变量,枚举常量
  • METHOD:方法
  • PARAMETER:方法的传参
  • CONSTRUCTOR:构造函数
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解
  • PACKAGE:package 声明上
  • TYPE_PARAMETER:可以用在 Type(类型)的声明式前
  • TYPE_USE:可以用在所有使用 Type(类型)的地方

Annotation 是如何起作用的

  • 编译器读取
  • 代码中通过反射读取 Annotation 信息

参考

Linux 客户端 for ShadowsocksR

有时抓海外的内容,直接 ssh 连,很快就被干掉,好在还有 ssr,暂时比较稳定。

网上有现成的搭建的方式,不难,不过害怕下次再操作,得重新折腾一遍,做了个 docker 镜像。

地址在此:https://hub.docker.com/r/suren1986/docker-ssr-client/。

使用 Docker 部署 php 开发环境

为什么会想起来用 Docker?

去年听说的 Docker,一开始看介绍没有看明白容器技术能做什么。最近有空再次研究了文档,手动试了试,才弄明白是个啥:一种给需要安装、配置操作系统的人的福音
我很喜欢安装操作系统、配置环境这种事情。无论是桌面还是服务器,每次遇到一个新的操作系统、一个新的机器、一个新的服务环境,从什么都没有一直配置到可以正常工作,特别有成就感。进度条不断前进,或者编译器不断输出看不懂的 log,总是特别有魔力。
但再喜欢的事情做个 100 遍,也就枯燥无谓了,盯着进度条变得枯燥无味。如果遇到些问题更是让人烦躁,如果是以前解决过的问题,又要 Google 的海洋里游荡一番才能解决。
桌面的操作系统,我现在没有这个问题,因为 Mac 有 TimeMachine,换新机器时,让 TimeMachine 自己跑个 1 小时,还是那个熟悉的环境,只是性能变得更强。
服务器的配置就没有那么幸运,每次还是得 apt-get update, apt-get install,各种 /etc/ 下面的 config 文件倒腾一番。好在现在的云主机供应商都有镜像,配置完了做一个镜像,下次开新机器就很方便。不过也有一些缺点:1. 我不会记得自己对操作系统做了什么操作,安装了什么,修改了什么,有些服务的镜像不能直接使用以前的,相同的问题还得再解决一次;2. 如果使用新云主机功能商的服务,所有的努力得再来一次。
Docker 很好的解决了这个问题。所有配置环境的操作,都放在文本文件中。做了哪些事情一目了然,通过版本管理系统,也能记录每次修改。而且,文本文件很小,使用新的供应商提供的服务时,直接下载安装即可,还是一模一样的环境。
Docker 对性能的影响很小。Docker 的容器,相当于一个独立的操作系统,但是它不是虚拟机,它依然依赖宿主机内核提供的服务,在宿主机的内核上,添加所需要的服务,再提供服务给使用者。

为什么用 Docker 搭建开发环境

最主要的原因,是直接上生产环境有风险,通过搭建开发环境练练手..不过用 Docker 的确能解决一些问题
我见过的 PHP 开发模式有 2 种:

  1. 本地开发。每个开发人员,在自己的机器上,安装 nginx, php, mysql, redis, etc,配置一番,然后开始开发,将所有工作结果通过版本控制工具合并。这个模式有个问题:大家的环境不一样,因为配置的原因,同样的代码,在不同机器上的表现是不一样的,给开发和 debug 带来不变;
  2. 集中式开发。用一台服务器,部署一个 nginx, php, mysql, redis, etc,每个开发人员将自己的代码上传到不同的目录,每个人分配一个 vhost,这样大家就能各干各的。优势自然是大家的环境都一样,甚至数据都一样,不太容易出现不同机器上的表现不相同的情况。但也带来了问题;一是服务在一起,数据在一起,会互相影响,比如我要测试一下数据库为空的情况,这时别人就没发工作了;二是开发人员得一直连着网,断网了就没法开发,就算现在网络发达,不会断网了,偶尔得在公司外面开发吧,运维得管理一堆 VPN 账号,也是很麻烦的事情。

现在有了新的思路:用 Docker 搭建开发环境。其实还是第一种,本地开发。用 Docker 解决了大家配置不一致的问题。因为 Docker 的配置文件都是一样的,不需要手动操作,理论上安装出来的环境是一样的。

如何使用 Docker 配置开发环境

首先得安装 docker 和 docker-compose。
docker-compose 让你对 container 做一些简单的配置,并且一个文件就能启动多个不同的服务。

**然后配置 docker-compose.yml ** 找个地方放这个文件,下面是配置示例。

version: '2'

services:

    nginx:
        container_name: nginx
        image: nginx
        ports:
            - 8000:80
        volumes:
            - /path/to/your/php_code:/path/to/your/code/in/container
            - ./nginx/sites/your_site.conf:/etc/nginx/conf.d/your_site.conf
    php:
        container_name: php_fpm
        image: suren_php_fpm:latest

    bg-sync:
        image: cweagans/bg-sync
        volumes:
          - /path/to/your/php_code:/source
        volumes_from:
          - php
        environment:
          - SYNC_DESTINATION=/path/to/your/code/in/container
          - SYNC_MAX_INOTIFY_WATCHES=40000
        privileged: true

    mysql:
        image: mysql:5
        volumes:
            - ./mysql/data:/var/lib/mysql
            - ./mysql/init.d/:/docker-entrypoint-initdb.d/
        environment:
            MYSQL_ROOT_PASSWORD: root_password
            MYSQL_DATABASE: ergedd
            MYSQL_USER: ergedd
            MYSQL_PASSWORD: ergedd

    pma:
        container_name: pma
        image: phpmyadmin/phpmyadmin
        environment:
            - PMA_HOST=mysql
        ports:
            - 8080:80

    redis:
        container_name: redis
        image: redis:3
        volumes:
            - ./redis/data:/data

这个文件的说明可以查看说明文档,有些特殊的解释一下:

  • bg-sync:Docker 在 Mac 下的共享磁盘,性能有问题,导致 php 运行特别慢,通过 bg-sync 折中一下。也有其他的解决方式,但正如 bg-sync 的说明文档所述,bg-sync 是能 work 里面最简单的一种。
  • mysql 中的 /docker-entrypoint-initdb.d/,通过共享文件到这个目录下,可以在 container 第一次被创建时,跑一些初始化脚本,比如倒入初始数据库之类的操作。
  • suren_php_fpm:latest:这是我自己做的 php_fpm 镜像,没有使用原版的,因为需要添加 mysql 插件。

有了这个文件后,运行命令 docker-compose up -d,所有服务就启动起来了,非常方便、整洁。