豪翔天下

Change My World by Program

0%

这几个月我正在阅读的是编程之美系列书籍,《架构之美》是我看的第一部。全书只有前面两个部分吸引了我,后面的基本上是食之无味地略过。后来想想,此书成书于2009年,有些思想以及对编程的认识,在那个时候可能还是特别有吸引力的,但是拿到现在,可能并没有那么大的吸引力,一是因为这本书的后面部分讲得并不深入,另一个原因是新时代的语言满天飞,老式的语言对于我这种半吊子来说毫无兴趣。

书的前面部分主要讲了一些架构的基本概念和原则。比如,美丽的架构所展示的一些普遍的原则:

  • 一处一个事实:重复导致错误,所以应该避免
  • 自动传播:由一些构建工具支持
  • 架构包含构建:架构不仅包含运行时系统,而且必须包含它的构建方式
  • 最少量机制:实现某个功能的最佳方式要视情况而定,但是美丽的架构不会追求“最佳”。
  • 构建引擎
  • 增长的阶
  • 抵制熵增

以及相应的更完整的解释:

  • 功能性(Functionality):产品向他的用户提供哪些功能
  • 可变性(Changeability):软件将来可能需要哪些改变?哪些改变不太可能发生,不需要特别容易进行这些改变?
  • 性能(Performance):产品将达到什么性能?
  • 容量(Capacity):多少用户将并发使用该系统?该系统将为用户保存多少数据?
  • 生态系统(Ecosystem): 该系统将与其他系统进行哪些交互
  • 模块化(Modularity):
  • 可构建行(Buildability): 如何将软件构建为一组组建,并能够独立实现和验证这些组件?哪些组件应该复用其他的产品,哪些应该从外部供应商处获得
  • 产品化(Producibiity): 如果产品将以几种变体的形式存在,如何开发一个产品线,并利用这些变体的共性?产品线中的产品以怎样的步骤开发等
  • 安全心(Security)

概念性的东西其实到处都有,但是在第一部分里面,即使是讲述概念方面,作者的表达方式也是非常让人有读下去的欲望的。于是顺着作者的思路就来到了一个伟大软件架构的诞生的历程,不过那几个软件奇葩的名字还真是让人费解。好在作者用了最有效最真实的方法来讲述一个软件架构如何做到完美的。要想往完美的架构迈进,首先要做的就是承认,世界上没有完美的架构,只有在不断演化中,不断适应需求中,架构才能越来越完美。其实架构本身就已经很美了,每次我看到同事画的或是自己画的架构图,都觉得真美呀,特别是当自己画的架构图能够一下子让大家都看懂的时候。正如我在实际工作中所体验的一样,本书也认为架构的输出图是最重要的,有了图,我们的工程师才知道该做什么,才知道为什么要那样做。由于保密的问题,我肯定不会把公司内部的架构图放在这里,但是相信我,我们工程师画的每一张架构图,都是特别美的。

以前的我会认为编程是一件十分简单的事情,因为无论什么语言,只需要花两三天看一下基本的语法就能用那种语言写一个东西出来,但是在实际的工作中,我才明白,编程最重要的绝对不是编程语言,编程语言反而是最次要的,最重要的是如何根据当前的需求以及未来可能出现的需求而设计出的“最完美的架构”。

语录:

软件的架构其实是和公司的组织结构及开发流程相互影响的。当然大多数情况下是软件的架构是被动者。但好的软件架构设计原则反作用于组织机构及开发流程也不是不可能的。

没有完美的架构。架构师就是力求做一个务实的“平衡美人”。不能一边坐拥着间接、长远才见效、容易视而不见的幕后优点,一边又对为了实现前者随之带来的小小应付成本挑三拣四,这样很容易捡了芝麻丢了瓜。。。

好的架构就是要分离关注点,也即“庖丁解牛,分而治之”。降低耦合性,这样复杂性也随着降低了,让参与系统各个方面的开发测试人员只需了解自己需要了解的模块,不需要了解整个系统,就能并行地进行工作了。只有这样才能开发出超越了单个人智慧所能理解的复杂软件生态系统平台。对于复杂系统的大部分参与人员:“知其然,也要知所以然”未必适用。

构架在最初构想的时候,可以脱离实际,思考出解决问题的最佳途径,但是在实施过程中,必须要考虑细节。

架构是一个过程,而非一个结果。

但是,你仍然可以不必过多担心功能就开始设计架构。你关注的是需要满足的品质。

Fred Brooks说,概念完整性是架构最重要的特征:“最好是让系统反映一组设计思想,而不是让系统包含许多好的思想,而这些思想却彼此独立而不协调”(1995)

注意:软件架构不是一成不变的。需要时就改变它。要想做到可以修改,架构就必须保持简单

像一座建筑或一个城市的物理架构一样,系统的架构必须适应环境,利用该架构创建的工件将存在于该环境之中。

在每一种情况下,我们都会先探索所有可能性,然后再做决定。我们会在“最后可能的时刻”做出决定,即不做决定的代价超过了实现该特征的代价。尽管如果一开始就用Spring,有些事情我们可能会做得不一样,但在后来加入它也没有让我们受苦。在早期的迭代中,我们关注的是发现应用想成为什么样子,而不是Spring希望我们如何构建应用。

如果,在采取了所有让任务能够由单人处理的方法之后,架构任务仍然巨大而复杂,不能由一人来完成,那么产品肯定是太复杂了,以致不实用且不应构建。换言之,单个用户必须能够理解计算机的架构。如果计划的架构不能由一个人设计,那它也不能被一个人理解。(1997)

例如,如果我们请你来设计一个“基于Web的应用”,你首先问我们页面布局和导航树,还是问下面这些问题: ·谁提供应用主机托管?托管的环境有什么技术限制吗? ·你想运行在Windows服务器上还是在LAMP栈上? ·你想支持多少并发用户? ·应用需要怎样的安全性?有需要保护的数据吗?应用将运行在公网上还是在私有的内部网上? ·你能为这些答案排列优先级吗?例如,用户数是否比响应时间更重要?

软件架构师的首要关注点不是系统的功能。

架构的最主要产出是什么?我的答案是:图。这里面有两层含义:一层含义是如同建筑师描绘的蓝图一样,用于引导实施者;另一层含义是架构师头脑中清晰的目标系统。如果架构师头脑中没有系统清晰的图像,他是没有办法把它画出来的。

看完《架构之美》过后就开始看《安全之美》,感觉大量的章节读起来的感觉都差不多,并没有深入的体会也没有感觉对我有什么启发。我想,主要的原因可能是我大学本来就是学习的计算机大类里面的信息安全专业,书里面的大部分内容以及相关推理和讨论,学校的教材里面就讲过。当然我推测,学校的教材可能也是借鉴了外国的著作,也有可能是我们学校所使用的教材本来就是高手编写的。

当然,绝对不能说这本书写得差,只能说此书在我现在的阶段所能给我的帮助太少了,如果我刚刚接触信息安全这方面,可能会特别感兴趣,也会学习到很多。不过,确实有一点,安全确实很美,但是这本书却一直没有像预期的那样透过哲学或是其他什么方式突出安全的”美“。后来,我去看了豆瓣对此书的评价,评分只有6.7,果然比《架构之美》的7.1要少。而且评价里面几乎都是差评,即使有好评,也仅仅是读者对安全的评价,并没有对此书做过多的评价。只希望最后一部《数据之美》能不再让我失望了。

我从小就是一个机械迷,喜欢木匠,喜欢硬件开发,喜欢各种DIY,有了自己的房子,我就能搭建自己的小小工作间了。

电器

电机

  • 舵机: 里面有一个控制板和一个普通电机以及减速齿轮。可以精准控制旋转角度,但是只能180度旋转。常用于机器人的手臂旋转等。
  • 减速电机: 和直流电机差不多,但是因为能减速所以扭矩非常大。
  • 直流电机: 普通的一直转的电机,扭矩比较小。
  • 步进电机: 能够控制旋转角度,扭矩属于中等。淘宝链接: 混合式57步进电机,扭矩2.3Nm,高75.5mm

  • 锂电钻: 相比于其他的电钻,主要就是携带方便,小巧玲珑,缺点是功率小,要想用来钻墙基本是不可能的。手上有一个12V不能变速的锂电钻,小幅度提升幸福感,电器的螺丝基本上可以,但是遇到需要强有力的螺丝的时候是凝不紧的,还得用手。试过用钻头钻木材,比较慢;而且夹持范围太小,1cm的钻头都夹不了。
  • 冲击钻: 和电锤原理一样,都是在旋转的时候做小幅度活塞运动。
  • 电锤: 冲击钻打不动的就用电锤了。
  • 石工钻头: 通体银白色,主要用于砖墙。
  • 木工钻头: 头上非常尖锐,主要用于木材。
  • 金工钻头(麻花钻头): 通体黑色,主要用于金属。

切磨

切磨当然是用切磨机,但是有时候也可以用电钻代替,有连接杆可以连接,但是用电钻的话并不方便实用。

  • 羊毛轮(羊毛抛光轮): 专用与抛光或修补擦伤的材料,如玻璃、陶瓷、石材、金属、塑料等
  • 切割片
  • 百叶轮:用来磨的

线

电流只与导线截面积有关。0.2平方的线是1A,0.3平方的线是1.8A,0.5平方的线可以通过2.5A/220V/550W,0.75平方的线是3.75A。

  • 杜邦线: 用于实验板的引脚扩展,可以非常牢靠地和插针连接,无需焊接,可以快速进行电路试验。
  • OK线:比普通导线细,一般用于电路板飞线,PCB跳线等,一般是30号规格,最大承受0.5A电流。价格都非常便宜。

继电器

一种电子控制器件,用较小的电流去控制较大电流的一种“自动开关”,在电路中起着自动调节、安全保护、转换电路等作用。当然,其实也能用弱电控制强电。继电器连线示意图:

继电器端口示意图

注意事项:

  • 继电器的VCC(接直流正极)与GND(接直流负极)是用来接模块的供电,必须是直流电。
  • IN端是模块触发端,当有触发信号时,继电器的输出端开关会闭合,就会通电。
  • 高电平触发(PNP触发): 正极触发,即在触发端与电源负极之间电压达到触发条件的电压时,继电器闭合。(例如,当IN端有3-12V时,继电器闭合,当IN端有0-0.5V时,继电器断开)
  • 低电平触发(NPN触发): 负极触发,即在触发端与电源负极之间电压为0V或接近0V时,继电器闭合。(例如,当IN端由0-4.5V时,继电器闭合,当IN端有大于5.5V时,继电器断开)

非电器

木料

铁料

钉子

  • 水泥钉

  • 膨胀螺丝(膨胀螺栓): 固定式灯具,如无特殊要求,墙壁开孔一般均为6毫米。

    螺栓规格(毫米) 钻孔尺寸 (毫米) 受力性能 (公斤)
    直径 深度 允许拉力 允许剪力
    M6 10.5 40 240 180
    M8 12.5 50 440 330
    M10 14.5 60 700 520
    M12 19 75 1030 740
    M16 23 100 1940 1440

2016总结

2017年是我的本命年,果然,在2017一开始的时候就遇到几件大事,和女朋友吵了一场很大的架,差点错过彼此。可以说,整个一月份都是在吵架、和好的过程中度过的,最后还是和好了,并且我也确定了她,过年的时候居然还订了婚,果然,吵架是情侣关系的一个坎,过去了,就一生一世,过不去,就只是路人。这里又一次,站在农历的过年前,写下了我的总结,再一次感叹,那些每次在新年一开始就写总结的人在最后的一个月里是多么的空闲。

今年,是我和她在一起的第二年,今年吵架的次数肯定是无数次的,吵到要分手的程度也有四五次吧,不过,基本上是我转正过后,准确地说是在我拿到现在这份期待已久的工资的时候。有了这份工资,我终于可以买到自己想要的东西了,也因为有了这份工资,无形中,自己被欲望所奴役,导致忘记了曾经那个理性的自己,忘记了曾经那个善良的自己,少了对她的疼爱,多了对房、车、以及所有希望买的东西的欲望。一直以来我都在忙着做自己的事情,在乎自己在乎的东西,总是觉得自己认为的就是对的,但是我并没有真正去了解她,并没有真正走进过她,被欲望遮住了双眼的我,甚至多次讽刺她,瞧不起她。即使这样,她都还是原谅了我,最终还是和好如初,这样的女人,我怎么可能不把她留在身边呢,所以,我们决定今年请双方家长一起过来团年,明年春节订婚。具体结婚的日期,我希望是2018年10月1日,因为那一天我生日,我在大概10岁的时候就算好了那一天是我的生日,从小学开始我就告诉身边的人,我要在那一天结婚,没想到,这个愿望还真有可能实现。而且晚婚假15天,那天我刚好25岁,真巧。

今年,我们买房了。一套走几分钟才能看到江的”江景房“,两室一厅,不算大,但是住我们倆,再加父母偶尔来一下,完全够了,而且阳台和厨房超大,以后可以更多的享受做饭和休闲的乐趣。旁边有两个公园,距离嘉陵江很近,非常适合养生,感觉住在这里可以多活好几年呢。贷款加上利息接近百万,要还款30年,其实没什么,刚需房,想买的时候就买,毕竟回头看去,只过了两个月,那套房子的价格又涨了我一年多的工资总数。另外,对于爸爸妈妈出钱付首付和花精力来帮我装修房子,还是只能说此生报不完的恩情。

今年,毕业了,正式工作了,虽然工作经验已经有了一年半,但是今年算是真正的开始工作了。毕业答辩有惊无险的过了,对于同班同学,由于大三才呆在一起,所以感情并不大,反而对以前那个班的同学感情比较深一点,当然,最深的还是室友,经常会想起他们,经常想起我在寝室飙歌,可惜,一切都回不去了。现在大家每天都有各自的工作,晚上回到家,大家也都很累,根本没时间大家联系了。不过还是希望大家,前程似锦。工作了三个月顺利转正,由于已经工作一年,对这些并无感,而且上家公司福利待遇超级好,哈哈。

今年,学日语的目标没有达成,连五十音都还不会呀。因为买房,所以给父母做全身检查的目标也没达成,结交IT界好友的目标没达成。。。。唯一达成的是博客日访问量10+盒SegmentFault声望1k+。当然,这些是我在今年年中定下的目标。每次,我的生命中有大事的时候,我就会每天想着它而不能干其他的事情,比如要毕业了,我心里一直想着毕业的事情,要买房了,一直想着买房的事情,要装修就一直想着装修的事情,这样一件接着一件,我觉得我每天都在浪费时间,突然觉得每个人的生命中都会遇到这样或者那样的大事,但可能只有我,需要花特别多的精力在上面,甚至不去想它就无法用心做其他的事情。无论怎样,希望明年的自己能够更强大,能够战胜拖延症。。。

2016,感谢你,让我变成今天的自己。

2017计划

豪翔天下

  • 简书: 1000+喜欢

  • 微信: 100+订阅

  • 微博: 粉丝+1000

  • 博客: 日均20+

  • 专栏: 1个

  • 阅读: 20+本

  • GitHub: 100+ star

  • SegmentFault: 3k+声望

  • 玩玩儿硬件开发

  • 搭建家庭NAS

  • 多做几个side project

  • 日语二级(这是我2014年定下的2015年的目标,2016年并没有完成。。。)

资产

  • 研究理财产品: 10+

  • 和女朋友存款1w+(婚戒、买车),(2017年1月26日,发年终奖了,婚戒已买)

  • 坚持记账

  • 把家装修成想要的样子

健康

  • 平均每天1w+步
  • 每周一次,运动量大一点的运动
  • 参加一次集体运动,如马拉松等

情感

  • 和女朋友至少一次出省旅游
  • 至少一次带家人旅游
  • 和女朋友多次省内旅游
  • 多去和老朋友聚聚会
  • 家人生日尽量回家
  • 带双方父母做健康检查

Aria2简介

Aria2是一个轻量级的多源多线程的跨平台的命令行下载工具,支持HTTP/HTTPs、FTP、SFTP、BitTorrent和Metalink等下载方式。当然,目测,国内更多用于百度云的下载,不过,自从我家里用了linux的nas,并且迅雷已经不能用的情况下,我也更倾向于使用aria2。

相比于you-get以及其他的下载工具,aria2最大的优点是其内部的连接数控制缓存控制能够明显提高下载速度,并且不会轻易失败

Aria2的安装

For Mac: brew install aria2

For Debian: apt-get install aria2

Aria2的使用

1
2
3
4
5
6
7
# 参数说明
--all-proxy=127.0.0.1:8181 # 设置http代理,包括了http/https/ftp
-m 100: 设置重试次数,默认是5
-c: 断点续传,如果有只下载了部分的文件那么继续下载
-k20M: 多大的缓存,默认是20M,建议修改为-k1M,因为网络环境不好
-s5: 使用多少个连接数,默认为5,建议为10
-x1: 每个服务器的最大连接数,默认为1,建议为16
  1. (可选)安装ria2GUI,这是Aria2的桌面GUI程序,集成了aria2c,支持多线程下载,为完成任务退出自动保存,支持PT/BT,可以显示整体下载速度。方便管理,从github进行下载,下载完成后直接解压打开就是个dmg应用了。
  1. 安装百度网盘的导出工具,谷歌浏览器插件下载地址,由于谷歌把它禁用了,得自己下载来使用(貌似是违反了谷歌插件的不能改变网页内容的条例)。

  2. 现在可以打开百度网盘,直接选择导出到rpc即可开始下载了。

重要概念

生产者(Producer)

消费者(Consumer)

消费消息。每个consumer属于一个特定的consumer group。使用consumer high level API时,同一个topic的一条消息只能被同一个consumer group内的一个consumer消费,但多个consumer group可同时消费这一消息。每个partition只会由一个consumer消费。

集群(Cluster)

宏观来看,Kafka主体包含的就是三部分: 生产者、消费者和集群,一个集群就是多个Broker的集合。

Broker

已经发布的消息就会保存在集群中的某个Broker中去。

Topic

用来区别message的种类,比如很多时候,与A相关的日志统一的topic定义为A,B相关的日志统一的topic定义为B,这样就不用一个一个单独地订阅了。物理上不通topic的消息分开存储,逻辑上一个topic的消息虽然保存于一个或多个broker上,但是用户只需指定消息的topic即可生产或消费数据而不必关心数据在哪里。

Partition

Kafka中每个Topic都会有一个或多个Partition,他是Kafaka数据存储的基本单元,每个Partition对应一个文件夹,文件夹下存储这个Partition的所有消息和索引。Kafka内部会根据算法得出一个值,根据这个值放入对应的partition目录中。所以读取时间复杂度为O(1)。分区的每一个消息都有一个连续的序列号叫做offset,用来在分区中唯一标识这个消息。一个topic可以保存在多个partition。Kafka会保证每个partition内部的顺序,但是不能保证跨partition的全局顺序,如果要保证全局有序,那么topic就只能有一个partition。如果一个group内部的consumer数量小于partition数量,那么至少有一个consumer会消费多个partition。当consumer数量和partition数量相等时效率最高。consumer数量不要大于partition数量,否则会有consumer空闲。consumer会自动负载到不同的partition。

  • 对于某一个topic,增加partition可以增加吞吐能力,但无法保证topic级别的有序性。

Segment

一个partition由多个Segment组成,一个Partition代表一个文件夹,一个Segment则代表该文件夹下的文件。Segment有大小限制,由log.segment.bytes 定义。

安装

docker方式安装

docker-compose.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
version: '2'
services:
zookeeper:
image: wurstmeister/zookeeper
environment:
JMX: 9000
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka # 这个镜像使用文档见https://github.com/wurstmeister/kafka-docker
ports:
- "9092"
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://:9092 # 这是重点,否则,在容器内部启动生产者消费者都会失败的
KAFKA_LISTENERS: PLAINTEXT://:9092
KAFKA_CREATE_TOPICS: "test:1:1" # 自动创建一个默认的topic
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" # 禁用掉自动创建topic的功能,使用上面的镜像,kafka的参数设置都可以以这样的方式进行设置
volumes:
- /var/run/docker.sock:/var/run/docker.sock
kafka-manager:
image: sheepkiller/kafka-manager # 如果要安装web管理工具可以同时安装这个,最后通过宿主机IP的9000端口进行访问,例如172.31.148.174:9000
links:
- kafka
- zookeeper
environment:
ZK_HOSTS: zookeeper:2181
APPLICATION_SECRET: "letmein"
ports:
- "9000:9000"
expose:
- "9000"

安装命令:

1
2
3
4
5
docker-compose up -d			# 默认只会有一个kafka实例
docker-compose scale kafka=n # 将kafka实例增加到n个,什么都不用修改,就能直接建立一个集群
docker-compose stop # 暂停所有容器
docker-compose start # 开启所有容器
docker-compose rm -f # 删除所有容器

kafka命令

kafka-console-consumer.sh

1
kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic test	# 启动一个消费者,监听test这个topic

kafka-console-producer.sh

1
2
kafka-console-producer.sh --broker-list localhost:9092 --topic test	# 启动一个生产者,直接输入消息回车即可发送消息了
kafka-console-producer.sh --broker-list localhost:9092 --topic test < access.log # 直接将文件内容传入kafka

kafka-consumer-groups.sh

1
2
kafka-consumer-groups.sh --new-consumer --bootstrap-server localhost:9092 --list	# 查看新消费者列表
kafka-consumer-groups.sh --new-consumer --bootstrap-server localhost:9092 --describe --group kafka-python-default-group # 查看某消费者的消费详情,这里的消费者名称就是kafka-python-default-group

kafka-producer-perf-test.sh自带的压测工具

1
kafka-producer-perf-test.sh --topic test --num-records 10000 --record-size 1 --throughput 100  --producer-props bootstrap.servers=localhost:9092	# 总共100条数据,每条大小是1

kafka-topics.sh

1
2
kafka-topics.sh --list --zookeeper zookeeper:2181		# 列出所有的topic
kafka-topics.sh --describe --zookeeper zookeeper:2181 # 查看集群描述

安全认证

Kafka可以配合SSL+ACL来进行安全认证: http://orchome.com/185

TroubleShooting

  • 容器内部启动生产者出现错误:[2016-12-26 03:03:39,983] WARN Error while fetching metadata with correlation id 0 : {test=UNKNOWN_TOPIC_OR_PARTITION} (org.apache.kafka.clients.NetworkClient)

    是因为docker-compose文件里面的宿主讥IP设置出错,如果是动态IP的话就没办法了,只能删除重新创建了

  • 启动生产者或者消费者出现LEADER_NOT_AVAILABLE:原因是没有执行docker-compose scale kafka=n

拓展阅读

之所以要叫超简短书评,是因为这本书我根本没看完,只看了前面几篇文章,我就毫无继续看下去的动力了。说来也怪,我看过那么多的书,还从来没有这种感觉。可能是最近身边的杂事太多,也可能是这本书确实不如期望中的那样好。给人的感觉就是作者在写身边的小故事,但是故事平平静静,并且平静中并没有什么惊喜,甚至我连他到底在讲什么都不知道。最后,和大多数散文集一样,此书书名“一生里的某一刻”同样取自其中一篇。然后我就直接跳到那一篇。果然,依然是云里雾里的。超简短书评,就这样吧,相信大家也能看出我对此书的态度了。

基本概念及程序框架

主要目录文件

AppDelegate.swift: 所有应用开始都有一个AppDelegate,是整个应用程序的一个代理。在应用启动的时候,最先被调用的就是这个AppDelegate中的applicationDidFinishLaunching方法,可以在这里做全局初始化,但一般为了保持代码的整洁,具体逻辑并不放在这里。

Assets.xcasserts:

Main.storyboard: 项目主要的UI文件

Info.plist: 项目的基础配置

*.h: 头文件。包含类,类型,函数和常量的声明

*.m: 源代码文件。可以有Objective-CCSwift代码

*.mm: 源代码文件。可以有Objective-C/C/C++/Swift

*.cpp: C++代码

组件IB(Interface Builder)

Labels and Text Field

Combo Boxes

Text Views

Sliders

Date Pickers

Buttons

Radio Buttons

Check Buttons

Image Views

定位

居中定位

1
# 直接选择该Object右下角的Align的Horizontally in Container和Vertically in Container全部设置为0,然后选择Update Frame修改为Items of New Constraints,再点击Add 2 Constraints button.

权限

info.plist权限列表,如果要用到相应的功能却没有添加相应的权限,很可能APP会直接闪退。以下列表来自于[槑头脑]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- 相册 --> 
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<!-- 位置 -->
<key>NSLocationUsageDescription</key>
<string>App需要您的同意,才能访问位置</string>
<!-- 在使用期间访问位置 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意,才能在使用期间访问位置</string>
<!-- 始终访问位置 -->
<key>NSLocationAlwaysUsageDescription</key>
<string>App需要您的同意,才能始终访问位置</string>
<!-- 日历 -->
<key>NSCalendarsUsageDescription</key>
<string>App需要您的同意,才能访问日历</string>
<!-- 提醒事项 -->
<key>NSRemindersUsageDescription</key>
<string>App需要您的同意,才能访问提醒事项</string>
<!-- 运动与健身 -->
<key>NSMotionUsageDescription</key> <string>App需要您的同意,才能访问运动与健身</string>
<!-- 健康更新 -->
<key>NSHealthUpdateUsageDescription</key>
<string>App需要您的同意,才能访问健康更新 </string>
<!-- 健康分享 -->
<key>NSHealthShareUsageDescription</key>
<string>App需要您的同意,才能访问健康分享</string>
<!-- 蓝牙 -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App需要您的同意,才能访问蓝牙</string>
<!-- 媒体资料库 -->
<key>NSAppleMusicUsageDescription</key>
<string>App需要您的同意,才能访问媒体资料库</string>

widget也都可以通过代码来设置他们的属性,例如

在applicationDidFinishLaunching函数内部写上

label1.stringValue = “wanghao”
button1.title = “Change Text”
Toolbar 菜单栏
view controller,也就是最开始的那种storyboard
container view,这里应该是可以放其他的view,相当于网页里面的iframe
custom view
vertical Split view/horizontal split view :水平或者垂直分割的view
Collection view:就是将一些列的数据以表格的形式展(包含了Collecotion Item),datasource可以直接outlets到view controller
object:就是一个对象,它可以与类帮顶起来(右上角custom class进行定义),定义了过后,又可以将它与下面的view相联系(control+拖曳,选择outlets),
table view:表格

Array Controller:好像是管理一组controller

menu bar:

要连接两个view,也是control拖曳

这个地方可以添加绝对定位或者想对定位 绝对定位想对定位

右边那个按钮则是像html里面的margin

上面是Day 2:auto layout

创建新的类的时候也可以同时创建xib文件,上面的collection controller,如果要有一个复杂的item那么久需要自己创建一个类

menu bar

直接在appdelegate里面:要设置application is agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
//let menu = NSMenu()
let popover = NSPopover()

func applicationDidFinishLaunching(...){
if let button = statusItem.button {
button.image = NSImage(named: "图片名称")// 在asets里面添加了的图片
//button.action = Selector("showWeatheraaa")

button.actoin = Selector("toggleWeather:")
}

//menu.addItem(NSMenuItem(title: "直接加", action: Selector("showWeather:"), keyEquivalent:"S"))
//menu.addItem(NSMEnuItem.separatorItem())
//menu.addItem(NSMenuItem(title:"quit", action: Selector("terminate:"), keyEquivalent:"q"))

// statusItem.menu = menu
popover.contentViewController =
}

func showWeather(sender: NSStatusBarButton){
print("我靠")
}

func toogleWeather(sender: NSStatusBarButton){
if popover.shown{
popover.performClose(sender)
}else{
if let button = statusItem.button {
popover.shownRelativeToRect(button.bounds, ofView: button, perferredEdge: .MinY)
}
}
}

新建了NSOBbject过后,要让它MainMenu .xib加载的时候久被加载,需要把它以object的形式添加到XIB中去

而这个object的Outlets就是相对应的view,比如Status Menu

同理如果创建的是一个NSview,那么一个view泽可以与之关联

安装与配置

  • angular不同的版本对typescript的版本要求是不同的,可以参考这里
  • angular升级是非常简单的,只要参考官方升级文档一步一步升即可
1
2
3
4
5
6
7
8
9
10
11
ng serve --host 0.0.0.0 --port 3000	# 启动,指定host,指定port

ng build --aot --optimization --build-optimizer # 编译项目
--aot # 默认为false,是否用提前编译进行构建
--optimization # 默认为false,使用构建输出优化
--build-optimizer # 默认为false,使用aot进行优化,推荐加上这个参数
--extract-css # 默认为false,从全局样式中提取css到css文件而不是放在js文件
--source-map # 默认为true,输出source-map文件
--vendor-chunk # 默认为true,将第三方包单独放到一个vendor文件中

ng build --deploy-url /app/ --deploy-url /app/ # 如果想要app运行在一个子路由路径下可以这样做

Module

1
2
3
4
5
6
7
8
9
10
11
@NgModule({
declarations: [
UserComponent
],
imports: [

],
entryComponents: [
DialogComponent, // 对于动态调用的组件,没有在html中调用,而是用js来调用的组件需要在这里声明,例如一些弹框,不声明的话,在动态编译的时候可能发现模板没有引用就不去加载了,但是我发现在lazy loading的时候,如果在child module中声明entryComponents不起作用,只能在app.module中声明才行
]
})

Lazy loading延迟加载

  • 延迟加载是基于页面路由的,每个路由都可以单独作为一个延迟加载,在进入页面的时候加载该页面所需要的组件
  • 如果实现了延迟加载我们在进入对应的页面后会发现新请求一个1.xxxx.js的文件,开头是一个数字。这就是当前页面的一些组件,同时我们会发现当前页面的组件在main.js中没有了
  • 如果我们的页面都是单纯的component而不是module的话需要做这些改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 在页面组件新建路由文件,例如dashboard.route.ts
export const routes: Routes = [
{path: '', component: DashboardComponent}
]

// 在页面组件新建module文件,例如dashboard.module.ts
import {routes} from './dashboard.route';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes) // 注意这里是forChild不是forRoot
],
declarations: [
DashboardComponent,
]
})
export class DashboardComponent { }

// app-routing.module.ts修改路由方式
const routes: :Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashhboard/dashboard.module').then(m => m.DashboardModule) // 注意这里是Module不是Component
}
]

// 最后在app.module.ts中,移除DashboardCompoent依赖即可

模板语法

数据绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 变量字符串连接
<img src="https://haofly.net/{{ image.url }}" />

// 动态绑定类
[ngClass]="{'myClass': selected}"
[ngClass]="type='xxx' ? 'mt-1' : 'mt-2'"

// 动态绑定样式
[ngStyle]="{'pointer-events': ok ? 'none' : 'auto'}"

<div [innerHTML]="string"></div> // 直接渲染html变量,默认会去掉元素内部的inline styling等属性
<div [innerHTML]="var"></div>
constructor(protected _sanitizer: DomSanitizer) {
this.var = this._sanitizer.bypassSecurityTrustHTML('string') // 直接渲染html变量,和上面的innerHTML不同的是,这样做,元素内部的inline styling等不会被去掉
}

控制语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// for 循环
<ul>
<li *ngFor="let item of items; let i = index">
{{i}}:{{item}}
</li>
<li *ngFor="let item of map | mapToIterable"> <!--对于key value的map进行for循环遍历-->
{{item.key}}:{{item.value}}
</li>
</ul>

// switch语句
<div [ngSwitch]="myvalue">
<div *ngSwitchCase="'aaa'">
...
</div>
<div *ngSwitchCase="'bbb'">
...
</div>
<div *ngSwitchDefault>
...
</div>
</div>

// ngShow和ngHide在angular 2+已经不支持了,可以直接这样做
[hidden]="myVar"

get方法/computed方法

  • 类似于vuejs中的computed
1
2
3
get 字段名() {
return this.firstname + ' ' + this.lastname;
}

表单Form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// js/ts文件
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';

export class MyComponent implements OnInit {
myForm: FormGroup;

constructor(private formBuilder: FormBuilder);

ngOnInit(): void {
this.myForm = this.formBuilder.group({
formFieldName: ['初始值', [Validators.required, this.checkName()]], // 第一个参数设置初始值,第二个参数是验证方法列表,注意如果有多个validator,后面的一定要用中括号包起来,否则会报错Expected validator to return Promise or Observable
字段2: ['', []],
字段3: new FormControl('', {
validators: [
this.aaaaaa.bind(this) // 自定义验证方法
],
updateOn: 'blur' // 失去焦点的时候进行验证
}),
字段4: [{value: '初始值', disabled: true}] // 如果要让某个字段disabled需要在这里做,直接在html上面disable可能不生效
}, {
validator: this.checkAll // 如果不是针对某个字段,而是针对整个表单,比如同时验证多个字段,那么可以在这里做
})
}

this.checkName(): any {
return (control: AbstractControl): { [key: string]: boolean } | null => {
return control.value >= 0 && control.value <= 2 ? null : {nameValueError: true}; // 如果出错可以返回一个key-value
};
}

this.checkAll(formGroup: FormGroup): any {
return (formGroup.value.formName !== 'new') ? null : {typeEmpty: true};
}

onSubmit(): void {
this.submitting = true;
this.myForm.get('field1').setValue(value); // 手动设置form表单字段的额值
if (this.myForm.valid) {
console.log('its ok');
}
}
}

// html中这样使用
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" (input)="inputChange" formControlName="formFieldName" [(ngModel)]="user.name">
<p class="form-warning" *ngIf="submitting && createForm.get('formName').errors">
<span *ngIf="createForm.get('formName').errors.nameValueError"> // 这是上面自定义的错误
Name Should be 1 or 2.
</span>
</p>
</div>
<div class="mat-form-field"> // 注意表单级别的校验error,不能写在field下面,后者不会显示出来,mmp
<mat-error class="form-errors" *ngIf="formGroup.hasError('wrongDate')" i18n
>The end date should be after the start date.
</mat-error>
</div>
<button type=submit">Submit</button>
</form>

filter过滤器

1
{{ timestamp * 1000 | date: 'yyyy-MM-dd'}} // 时间格式化

样式

1
2
3
4
// 如果要覆盖第三方组件的样式,可以用::ng-deep,并且为了防止把其他组件也覆盖了,可以加:host前缀将样式覆盖限制在当前的宿主元素上面去
:host ::ng-deep .xxx {

}

组件通信

父组件至子组件通信

  • 直接用@Input
1
2
3
4
5
<app-child [field]="value"></app-child>

export class ChildComponent {
@Input() field: any; // 根据我的测试,子组件可能无法在contructor或者onInit中获取到这个值,因为这个值可能是动态的,所以最好在子组件创建一个get XXX()方法来获取变化后的值
}

子组件至父组件通信

  • @Output EventEmitter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<app-child (field)="onChildClick($event)"></app-child>

export class ParentComponent {
onChildClick(field) {
console.log(field);
}
}

export class ChildComponent {
@Output() field = new EventEmitter<String>();

onClick() {
this.field.emit('click');
}
}
  • @ViewChild不仅能获取子组件的字段,还能直接使用子组件的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<app-child></app-child>

export class ParentComponent {
@ViewChild(ChildComponent)
private childComponent: ChildComponent

onTest () {
this.childComponent.onTest1();
}
}

export class ChildComponent {
onTest1 () {}
}

不相关的组件通信

  • 创建service来通信,复杂的应用场景这个还是用得比较多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 需要先找个地方新建一个service
@Injectable()
export class MyFieldService {
private myField: Subject<string> = new Subject<string>();

setMessage(value: string) {
this.myField.next(value)
}

getMessage() {
return this.myField.asObservable()
}
}

export class Component1 {
constructor(private myFieldService: MyFieldService)

onFieldChange() {
this.myFieldService.setMessage('new value');
}
}

export class Component2 {
constructor(private myFieldService: MyFieldService) {
// 需要特别注意的是,如果回调函数报错了,之后就不会监听了,造成了只能监听一次的假象
this.myFieldService.getMessage().subscribe((value) => {
...
}
}
}

生命周期

依次是

  • ngOnChanges(需implements OnChanges): 当设置或重新设置数据绑定的输入属性时响应,但是当组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用ngOnChanges()
  • ngOnInit(需implements OnInit)
  • ngDoCheck
  • ngAfterContentInit()
  • ngAfterContentChecked()
  • ngAfterViewInit(需implements AfterViewInit): 当初始化完组件视图以及子视图或包含该指令的视图之后调用,只会调用一次
  • ngAfterViewChecked
  • ngOnDestroy

扩展

  • @angular/flex-layout: angular的flex布局组件,能够很方便地实现flex响应式布局

    1
    <div fxLayout="row" fxLayoutAlign="space-between"></div>

事件

Angular1里元素绑定点击事件用ng-click,但是Angular2里元素绑定点击事件用(click),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// click事件
<button (click)="toggleImage()">

// input事件是指输入的时候
// change事件是指内容改变以后(离开焦点)
<input (input)="onInput()" (change)="onChange()"
(keyup)="onKeyUp(event)" // 键盘输入事件event.target.value可以获取input的value
>

<!-- select元素点击获取选择的值 -->
<select (change)="onChange($event.target.value)">
<option *ngFor="let item of devices | keyvalue" value="{{ item.key }}">{{ item.value }}</option> <!--keyvalue过滤器将字典转换为key value对象的形式-->
</select>

// keydown事件指定键,例如按下回车
<input (keydown.enter)="" />

网络请求

  • angularjs的网络操作由HttpClient服务提供,在4.3.x开始使用HttpClient代替Http
  • angular的http请求返回的是一个Observable(可观察对象),在被消费者subscribe(订阅)之前,不会被执行。subscribe函数返回一个subscription对象,里面有一个unsubscribe函数,可以随时拒绝消息的接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
constructor(private http: HttpClient) {}
ngOnInit(): void {
// 必须使用subscribe才会真的去发送请求。每次调用subscribe可以发送一次请求,也就算是说要发送多个请求,直接在最后那subscribe就可以了。

this.http.get('/').subscribe(
data => {},
error => {
error.json // 获取json格式的错误相应
} // catch error
);
this.http.post('', body, {}, {params: new HttpParams().set('id', 3')}); // 添加url参数
this.http.post('', body).subscribe(...); // post请求
this.http.post('', body, {headers: new HttpHeaders().set('Authorization', 'my-auth-token')}); // 设置请求头
this.http.get('').subscribe(
data => {}
err => {'错误处理'}
);
this.http.get('').retry(3).subscribe(...); // 设置重试次数
this.http.get(''). {responseType: 'text'}.subscribe(...); // 请求非json数据

// 设置自定义的超时时间
import { timeout, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

this.http.get('').pipe(timeout(2000), catchError(e => {
return of(null); // 需要注意的是,这里的of的参数会传递给subscribe的res作为返回值
})).subscribe((res) => {});

await this.http.get('').toPromise(); // 将网络请求转换为promise就可以用promise的await语法了

// 如果一个函数需要返回一个Observable对象,但是又根据条件来进行http请求,条件满足直接返回结果可以用of来封装一下
if ([condition]) {
return of('result');
} else {
return this.http.get('');
}
}

httpclient全局error handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 新建一个http-interceptor.ts文件,或者其他名字都可
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { _throw } from 'rxjs/observable/throw';
import 'rxjs/add/operator/catch';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req)
.catch(errorResponse => {
if (errorResponse.error && errorResponse.error.msg) {
...
}

throw errorResponse;
});
}
}

export const ErrorInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true,
};

// 然后在app.module.ts中声明这个provider即可
@NgModule({
providers: [
ErrorInterceptor
]
})

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<input #photoUpload type="file" accept="image/*" (change)="onInput($event)">
<button class="primaryButton" (click)="uploadImage()">Upload Image</button>

export class MyComponent {
@ViewChild('photoUpload') adminPhotoUpload: ElementRef;

uploadImage(): {
const files = this.photoUpload.nativeElement.files;
const formData: FormData = new FormData();
formData.append('file', file, file.name);

const headers = new HttpHeaders();
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');

return this.http.post(`${apiURL}/api/storage`, formData, {
headers
});
}

// 或者这样做
onInput(event): {
this.file = event.target.files[0];
}
}

单元测试

所有的单元测试文件均以.spec.ts结尾,该文件具体语法规则如下:

1
2
3
4
describe('test haofly"s function', () =>{
it('true is true', () => expect(true).toEqual(true));
it('null is true', () => exect(null).not.toEqual(true));
});

推荐扩展包

ngx-dropzone

  • 拖拽上传文件组件

ngx-socket-io

  • Socket-io扩展
  • 有一个问题是该第三方包现在是支持extraHeaders的(支持自定义header传入后端),但是却没有发布到npm仓库,参考这个issue,下面有人提出解决办法,参考这里,但是登录的时候还没有token,所以最好是在组件的init里面自己new一个Socket对象吧

TroubleShooting

  • Cannot read property ‘stringify’ of undefined: 在模板中无法直接使用JSON等原生对象,可以在constructor()中传入:

    1
    2
    3
    public constructor() {
    this.JSON = JSON;
    }
  • can’t bind to ‘ngSwitchWhen’ since it isn’t a known property of ‘template’: ngSwitchWhen已经被ngSwitchCase替代了

  • **can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’: ** 尝试将FormsModule添加到@NgModuleimports

  • ng: command not found: npm install -g @angular/cli@latest

  • URLSearchParams is not a constructor: 通常是因为引用URLSearchParams是通过import { URLSearchParams } from "url"引入的,但其实它早就内置于nodejs中了,可以不用写import语句直接用就可以了

  • 相同路由改变query params页面不跳转: 这是和很多单页框架一样的特性,这个时候可以用window.location.search进行页面刷新或者通过监听请求参数的变化来重新获取数据,例如:

    1
    2
    3
    4
    5
    ngOnInit() {
    this.route.params.subscribe(params => {
    this.service.get(params).then(...);
    }
    }
  • **ExpressionChangedAfterItHasBeenCheckedErrord: Expression has changed after it was checked.**:这是因为在子组件里面直接改变了父组件的值,通常是在ngAfterViewInit或者ngOnChanges中,因为这种改变可能会导致无限循环,所以是禁止的,但是如果确保不会发生无限循环,可以将改变的语句写到setTimeout中去

  • 给用代码生成的元素绑定事件/addEventListener需要使用.bind方法才能在回调函数内部使用this:

    1
    2
    3
    4
    5
    6
    7
    ngAfterViewInit() {
    document.querySelector('my-element').addEventListener('click', this.onClick.bind(data, this));
    }

    onClick(data, event) {

    }

扩展

现在是2016年11月22日,毕业大约5个月,外面飘着小雨,突然想起以前好像每次都喜欢在这样的场景下看着书思考人生呢。细想毕业的这几个月,感觉我的人生是不是走得太快了。
原以为,毕业以后,至少能每周都找个地方玩儿吧,周末约上三五个好友,踏踏青,赏赏水,坐坐长江的邮轮,顺便回南山看看母校的樱花。原以为,毕业以后,至少能挥金如土吧,刚签约的时候感觉自己的工资和同学比起来虽然不高,但是相比于大学每个月的生活费,那可不止三四倍呀,是不是很快就可以过上想买就买的日子了呢。原以为,毕业以后,至少能经常给家人朋友买点礼物吧,每个人生日的时候我都能当面送上一份特别的礼物,给他们来个惊喜。
但是,生活在这短短的时间里给我泼了好几大盆的冷水。
虽然每天朝九晚六,可以睡到八点半再起床,但是每天还是不够睡。即使是运气好不用加班,六点就能下班,也实在不想拖着疲惫的身体去找好朋友逛吃逛吃。看似每天工作只有7.5小时,实际上,剩下的时间都只能用于恢复精力。只想在家躺着,哪儿也不去,能够躺在舒适的床上才是我最大的希冀。周末?哪里来的周末。周末得坐长途车去看女朋友,女朋友比我更累吧,周末都得上班,所以周末必须好好陪陪她,陪伴的形式就是陪她上班。没有踏青,没有赏水,只有偶尔互相之间温柔的眼神。那是我每周长途跋涉唯一的安慰。
是谁发明的挥金如土这个词,难道不应该是挥金然后吃土吗。白领的意思就是每个月发了工资还了信用卡然后惊讶的发现,我靠,这个月工资又白领了。总有那么多花钱的地方,房租、水、电、气、长途车费、吃,基本上每个月会有1/3的钱会花在能让我活下去的这些需求上。接下来的1/3,偶尔吃个大餐,偶尔买个礼物,偶尔送个红包,基本上也花出去了,再剩下的1/3可以存下来,然后隔一两个月,心血来潮想买一个大件,然后,就没有然后了。买不起礼物,记住了每个朋友的生日,但是朋友生日的时候顶多说一声生日快乐,朋友结婚或者生孩子,有时候连“赞”都不敢点。
有位室友遇到公司裁员,丢了工作。对,就是那个我认为他是我大学见过最聪明的一位室友,居然被公司开除了,我完全不敢相信。听到这个消息的那天,我情绪很激动,想立马给他找到一份工作,但是问了好几家,基本上对工龄的要求都至少是两年。这可是一位刚毕业的本科大学生啊,哪儿来两年的工作经验呢。还好,写这篇文章的时候,他应该已经过了某个公司的的面试了,不过,那个地方,真的好偏远。
室友被离职的同一天,我另一个朋友也打电话来了,说他准备报一个培训班,从零开始学开发,问我要一些建议。这位朋友是我从小玩儿到大的,碰巧连所学的专业也相近,都跟计算机有关,不过我偏软件,他便硬件。毕业后他去了格力,结果发现里面有好多不可描述的内部危机,所以干脆直接回家乡发展,但是,同样的,也是一个工作经验连半年都没有的人,甚至他连基本的技能也差好大一截,所以不得不去报培训班。我一直觉得他比我努力好多倍,但总感觉他的运气总是不好,总是会在人生面对重大决策的时候或者人生将要迎来更加光明的时候生活给他泼一盆冷水。
更甚的,在前面的文章中有提到,我的妹妹去世了,一个二十出头的女孩子,刚当上妈妈就离开了这个原本美丽的世界。
相比于他们,或许,我算是很幸运的了。毕业以后找了一份“相对”稳定的工作。只能说相对,这两年是IT业的寒冬,谁也说不准明天会怎样,唯一知道的是如果要裁员,多半是拿新人开刀。另外,我还在重庆买了房子,走在了百分之九十几的同学前面了。贷款50万,分30年还清,利息大概30万。为什么那么急着买房子?说简单点,一是怕房价继续上涨(从去年的趋势看,基本上我要是提前一年买房,我可以少奋斗一年,这一年还不吃不喝才能补上那个差价),二是害怕突如其来的婚姻,不是不想结婚,只是还没准备好,不过,由于某些原因,我还是选择提前准备一下,我不想我的孩子一生下来,就呆在别人的房子里。最近,公司开始从PHP转Java了,动作挺大的,而我,确是一个PHP程序员,看样子我不得不去学曾经最讨厌,黑得最多的一门垃圾语言了。
我知道,当我们进入社会后,会面临各种各样的压力,各种各样的困难,各种各样的挫折。这些我真的都知道,只是,真的没想到,这一切会来得那么快,以迅雷不及掩耳之势一棒子打在刚脱离“青春”的我们头上,把我们打得晕头转向,不知所措。长夜漫漫,夜晚越来越睡不着了,想好多事,憧憬一下未来的样子,反正,就是不敢想现在,害怕发现现在的自己正在远离曾经的梦想。