自学成为软件工程师

这是一本如何通过自学成为软件工程师的简明教程,主要包含以下内容:

  1. 基础:计算机科学
  2. 掌握 Linux: The Arch Way
  3. 选择一把合适的剑:Rust
  4. 协作:代码与项目管理
  5. 解决问题:工程师思维

程序员不需要参加资格考试,只要你有编程经历,并有自己的作品,别人就认可你是程序员。

Tips: 这里需要一张手绘图说明内容架构和学习步骤。

绘图工具:https://excalidraw.com/

基础能力的三板斧

基础、基础、基础 一切要从基础做起,不然进阶的部分就发挥不了效用。

《最后一课的演讲》- 兰迪·波许

对软件工程师的定位有所了解后,有哪些基础能力是必备的呢?一切事物都要从基础做起,如果基础不是很牢固,在职业生涯中很容易遇到瓶颈,能力的天花板也会很快到达。

从理论知识到工程实践,软件工程师需要的能力可以从三个方面来总结:

  • 扎实的计算机学科基础
  • 熟悉 Linux 及良好的系统编程能力
  • 工程实战、综合能力素养

还有一些因素也很重要,比如英文水平,对硬件感兴趣、审美水平(对视觉设计、UI/UX和人机交互感兴趣),数学以及人文素养等,但是不同的人天赋、受教育水平和兴趣都有差异,如果某些方面你很擅长,可以把它发挥到极致,说不定能形成你独特的工作风格和人格魅力。

学习动机

做任何事,首要的是想清楚自己的目标(Why)

作者从事这个职业主要有三个原因:

  1. 想拥有一种改造世界的工程能力,这种欲望跟去制造实物产品,或成为硬件创客是相通的。
    • 个人可以通过创造的事物产生深远的影响,创造带来的成就感亦是很重要的一部分。
    • 但并没有黑客那种想掌控或者破解一个系统的驱动力,对网络安全感兴趣的同学可能会有这种动机。更像武侠世界中,少年想习得绝世武学。
  2. 编码过程需要高度专注和抽象思考的能力,专注也是克服时间本身的一种方式,对编程本身会产生有一种类似价值观或信仰的热爱。
  3. 现实的意义:软件和工程层面的实践有助于练就洞察事物本源的能力,对技术创业有很大的助力。

这些思考和总结,源自自身有限的行业认知和学习,以及大量工程师招聘要求,聊作参考。

这趟学习之路是一个轻松的路程,你也不需要知道什么大纲,把它当作一份已经踏上旅途的前辈给你的探路指南,有空闲时间就可以拿出来,跟着指引,动起手,开始探索自己的旅途。

简而言之,无论是出于热情还是其他原因,有自驱性才能学的进去。

在线阅读

在开始阅读之前,如果你对岗位称呼困惑,可以先读读这篇文稿:什么是软件工程师?

本地预览

mdbook serve --open

本书使用 mdBook 生成,使用前需安装好 Rust 环境及 mdBook

版权说明

Each file included in this repository is licensed under the CC BY License.

感谢

  • https://emoji.fly.dev/

基础:计算机科学

软件虽“软”,背后却是有严密的逻辑体系作支撑,人类发出的指令,经过编程语言的抽象,到计算机的执行,离不开数学、算法、计算机体系结构等理论知识的发展。

可计算性理论图灵生平作为关键线索,结合了解一下计算机发展史,可以对计算机科学的历史有一个全局的认知,了解计算机是从哪里来的, 又如何发展到今天。你会发现计算机与数学逻辑的渊源,图灵机、信息论与冯诺依曼架构等大批关键词会进入你的脑海。

说不定你会对某块内容产生极大的兴趣和热情。

深刻理解事物,要追本溯源(第一性原理),以发展的眼光把握事物的发展史,站在巨人的肩膀,站得高,望得远。

基础不稳的缘由

很多计算机专业的学生在本科教育阶段遇到是上课只会读 PPT 的老师,加上同学之间又没有几个对计算机本身都感兴趣的伙伴合作学习或者做项目,就会因此丧失了对计算机的兴趣,最后变成突击复习应付考试,并没有把知识转化为能力

还有很一大部分工程师并没有接受过完整的计算机学科训练,对基础知识的学习只是来自于短期培训、自学和工作中的碎片化积累,无法在职业生涯中走的更远。

不过好在软件行业注重实践,网络上亦有优质的学习资源和社区,你完全可以自学成才,基础不扎实并不是无法挽回的问题,亦不依赖什么天赋。

许多表面上的“天赋”,本质上是思维模式的不同,以及在此基础上随着时间,和滚雪球一样衍生出的“阅历”差异,而不是什么真正的天赋凛异。(知乎用户)

你可以伴随着好奇心,一点点去深入一件事情背后的原理与逻辑,在实践与应用中逐步搭起的知识框架,这比在课堂上被动接收的一个个孤立的知识片段会坚实且具有生命力许多,从而在知识的实际应用与进一步拓展中显得更加游刃有余,并且在面对未知的事物时有更大的力量与精准度去直击事物的本质。

初学入门,兴趣和热爱最重要

如果你对计算机知之甚少,也没有接触过编程,简单、轻松而全面的入门课程是最好的选择,一上来让你去学会某种编程语言,可能会让你从此对编程丢掉信心。

Harvard CS50's Introduction to Computer Science

计算机科学和编程艺术的一场轻松之旅,我是 CS50 的爱好者,我愿称之为最棒的计算机科学入门课,每年都会刷最新学年的视频,而且你一定会被主讲教授 David J. Malan 的个人魅力感染,而且上完这个课程,你会非常自信的去尝试做软件或继续其他领域知识的学习。感受一下讲师的热情

Image.png

B 站上面有 CS50 年的课程视频,感谢字幕组做了双语言翻译,少了语言的门槛:

CS50 每年都会开新的网络课程,如果你英文不错,可以考虑直接参加 2022 年秋季学期,跟哈佛的小伙伴成为同学,一起做项目更有意义:

不用担心自己不懂编程,跟着做项目是最关键的

还有一些非常不错的经典入门课程推荐:

额外的一些资源推荐,如果你还在大学校园,应该会感兴趣:

  • csdiy.wiki 一个北京大学信科专业同学在大三(2021 年)整理的一份计算机自学指南,如果你还是学生,想寻找同龄人一起组团学习,这里是不错的选择:CS自学指南
  • CrashCouse 计算机速成课系列视频,可以当成科普和学科概览视频:【计算机科学速成课】[40集全/精校]
  • 0xFFFF 这是一个面向计算机爱好者的学习交流社区,发源于华南师大计算机学院,目前汇聚了一些来自各地高校、企业乃至于中小学的计算机爱好者们。

Linux C编程一站式学习

Stanford CS110L: Safety in Systems Programming - CS自学指南

Teach Yourself Programming in Ten Years

【视频】Hubris:为健壮性开发操作系统

Hubris 是一个用于深度嵌入计算机系统的小型开源操作系统,例如:我们的服务器替代基板管理控制器(BMC,Baseboard Management Controller)。

本次演讲将概述 Hubris 的设计、Hubris 应用程序的结构以及我们在此过程中学到的一些亮点。

视频链接,https://talks.osfc.io/osfc2021/talk/JTWYEH/

Teach Yourself Programming in Ten Years

文章总结

几本技术与人文的书籍推荐:

禅与摩托车的维修艺术

技术呆子

https://brilliant.org/

计算机如何运转

想象一下,当你在浏览网页或者和朋友视频聊天的时,在漂亮的界面下面程序到底如何运行的一无所知。但是你决定成为一个软件工程师,你就要开始了解这些内部到底是如何运转的:

  • 机器之间是如何通信的?数据传输和存储的方式?如何保证通信安全?
  • 鼠标点击操作是如何变成一堆计算机执行指令的?
  • 常听的那些计算机零件,芯片、CPU、GPU、内存等是如何协同工作的?...

虽然不用像硬件工程师熟悉集成电路设计,焊电路板,但是对于计算机的组成、运转机制、程序编译过程了然于胸、非常通透,绝对是有益无害的投资。

主要需要学习以下内容:

1)计算机组成(Computer Organization):计算机组成强调的是有关控制信号(怎样控制计算机)、信号传递方式以及存储器类型等问题,涵盖了有关计算机系统的物理构成的各个方面。

  • 回答:计算机是如何工作?

2)计算机体系架构(Computer Architecture):集中讨论计算机系统的结构和行为,主要涉及程序员熟悉的系统实现的逻辑方面内容。其包括许多基本要素,如指令集和指令格式、操作码、数据类型、寄存器的数目和类型、寻址方式、主存储器的访问方式和各种I/O机制等。对于某个特定的计算机,计算机体系结构就是其各硬件部分的组合,再加上其指令集体系结构(ISA,instruction set architecture)。

  • 回答:怎样设计一台计算机?

3)编译原理,编译原理是比较枯燥的一部分知识,包括词法分析、语法分析、语义分析、运行时环境、寄存器分配、代码优化与生成等内容。

4)操作系统(Operate System)我们下一节单独聊。

学习课程和资源推荐

关于编译原理,机客时间的付费课程(非广告),适合工作几年的工程师补课: 编译原理之美, 时间和精力充足,也可以考虑啃斯坦福的 CS143 Compilers:

对于初学者来说,Compiler Construction: Principles and Practice 比龙书可能更友好一些(需要读英文版),《Parsing Techniques》也有很多人推荐,没了解过:

如果想学习编译原理,不妨也了解一下: LLVM Tutorial: Table of Contents — LLVM 15.0.0git documentation

CPU Architecture 架构

依旧在使用的 CPU 架构

  • x86_64, 大部分计算机使用
  • 基于 ARM64 的 ISA(指令集架构)
  • MIPS:大多数路由器使用
  • 基于S/390的大型机(现在改名为z/Architecture
  • 嵌入式系统
    • AVR(Arduino 设备)
    • SuperH(土星、Dreamcast、卡西欧9860计算器)
    • 805
  • RISC-V

定义特征上的区别:

  • 字的大小(word size)。8、16、31、32、64位
  • 设计风格(design style)。RISC(指令少,操作简单),CISC(指令多,执行复杂的操作,VLIW(指令长,同时并行做很多事情)
  • 存储架构(memory architecture)
  • 许可成本:RISC-V是开放的,可以免费使用
  • 特性集(features set):有些特性在特定架构平台有特定的支持。比如,浮点数(x87)、加密(AES-NI)、支持本地高级字节码执行(Jazelle、AVR32B)、矢量计算(SSE、AVX、AltiVec)。

动手制作 CPU

一些有趣的 homemade CPUs 项目

Virtual x86

JSLinux

Ben Eater

GitHub - darklife/darkriscv: opensouce RISC-V cpu core implemented in Verilog from scratch in one night!

逻辑电路设计工具 🔧

GitHub - logisim-evolution/logisim-evolution: Digital logic design tool and simulator

GitHub - hneemann/Digital: A digital logic designer and circuit simulator.

I/O Ports

I/O Ports - OSDev Wiki

BLASTER Variable

MMIO(内存映射的输入/输出)

Image.png

简单寄存器、指令

16 个 32 位的寄存器,编号 r0 到 r15:

  • r0 - r7: 低位寄存器,常用位置
  • r8-r15: 高位寄存器,只能用特定的指令进行操作
  • r13:栈指针 sp - Stack Pointer
  • r14: 链接寄存器 lr - Link Register
  • r15: 程序计数器 pc - Program Counter

指令分类,每一类都包含一个相同的头部(common header):

  • ALU (算术与逻辑)运算
  • load/store 指令
  • 栈操作指令
  • 分支指令(有条件和无条件)

总线 Buses

三态逻辑来实现的:一个信号要么是0,要么是1,要么是Z(发音为 "高阻抗")。Z是 “较弱”的信号,即,如果你将Z信号与01信号相连,将输出后者。

电路设计实例

{direction}R{sign}{mode} {destination}, [{base}, {offset} 指令的组件:

  • {direction}:不是 LD(load),就是 ST(store)
  • {sign}:要么不做(不扩展),要么就是 S(符号扩展值,以填充32位)
  • {mode}:要么是 nothing(全字,32位)和 H(半字,16位),要么是 B(字节,8位)
  • {destination}:目标寄存器,要读出/写入
  • {base}, {offset}:内存中的地址(将是两者的值之和)

ldrh r1, [r2, r3] 指令的编码示意:

Image.png

opcode 是一个 3位的值,用于同时对{direction}, {sign}{mode}进行编码。

电路图:

Image.png

  • 三个寄存器(Rd, Rb, Ro)在各自的位置(0-2, 3-5, 6-8)从指令中被读取,并被送到相应的全局通道(RW, RA, RB)。
  • opcode被解码,以检查它是一个store000,001,010)还是一个load(剩余值)。
  • opcode被解码以找到模式的值。0代表字,1代表半字,2代表字节。
  • opcode再次被解码以找到符号的值(仅对操作码011111来说是真的,所以我们可以检查最后两个比特是高位)。

Instr0708隧道是这个组件的激活引脚;如果当前指令属于这个指令组,它就是高电平。

内存 Memory

CPU的语言是汇编指令,这些指令有一个固定的、定义好的编码。

内存操作的简化:{load value from address", "store value at address"}.

内存的工作原理

Image.png

全局内存需要在任何时候要求任何数量的字节,是动态的,以堆(Heap)的形式存在。

本地内存用栈(Stack)的方式增长和收缩,有 push (增长)和 pop(缩小)操作,不需要对分配的内存块做任何登记,唯一需要关注的是该堆栈的深度,也就是栈的长度。通常的做法我们在内存中的某个地方设置为栈的起点,并在某个地方(例如,在一个寄存器中)保留一个全局变量,该变量包含栈最顶层的项(topmost item)在内存中的位置:栈指针(在ARM上为sp,或其全名为r13)。

栈的增长方向。

::寻址模式和内存对齐::

如果你想访问栈上的内存,你通常会访问栈顶部的东西(例如你的局部变量),所以你不必给出完整的内存地址(大),而只需要给出相对于栈指针的数据距离(小)。::这就是sp-relative寻址模式::,看起来像ldr r1, [sp, #8]

假设你大多会存储4字节或更大的东西,所以我们会说栈是字对齐(word-aligned)的:所有东西都会被移动,以便地址是4的倍数。

函数调用 Function Call

在汇编中,调用函数的最简单方法是通过使用jump

如何解决跳过去,如何回来的问题:

使用一个全局变量(即寄存器)来存储调用者的地址,并有一个特殊的跳转指令,将寄存器设置到当前位置(链接),这样我们就可以在以后回到它(分支)。在ARM上,这就是bl(branch-link)系列指令,该寄存器被称为链接寄存器(缩写为lr,昵称为r14

  • 对嵌套调用不起作用! 从另一个被调用的函数里面调用一个函数,值会被覆盖。
  • 当进入一个函数时,在栈中为局部变量分配空间,但也为必须保留的寄存器分配空间,当退出时,原始值从栈中放回到寄存器中。

设备 Devices

向地址0xFFFFFF00写一个字节将在终端显示器上显示一个字符。

从地址0xFFFFFF18中读取一个字节,就可以知道键盘缓冲区是否为空。

参考资料

Crabs All the Way Down: Running Rust on Logic Gates

吃透操作系统

我们开发的软件并不能直接去控制硬件资源,需要找一个代理人/媒介去管理硬件资源和软件的交互,就像一个资源管理器(resource collector),这就是操作系统存在的意义。当我们按下开机键后,位于电脑主板上的固件 ROM 中的启动程序(Boot Loader)会运行,启动程序会加载操作系统的启动程序,进而把整个操作系统加载到内存中并开始执行操作系统。

操作系统也是一种软件,但是操作系统是一种非常复杂的软件。

操作系统提供了几种抽象模型:

文件:对 I/O 设备的抽象 | 虚拟内存:对程序存储器的抽象

进程:对一个正在运行程序的抽象 | 虚拟机:对整个操作系统的抽象

操作系统课程包含的主要内容:

  • 操作系统运行时(程序中断、定时器)
  • CPU 调度
  • 进程(同步、互斥)、线程、管程(Monitor)
  • 存储管理
  • 文件系统
  • I/O 输入
  • 死锁
  • 信号量编程

读书、看视频课程都可以加深对操作系统的了解,经典书籍:

不过我们也可以学习一些跟得上时代的内容:

推荐课程 #1:MIT 6.S081: Operating System Engineering

MIT PDOS 实验室开设的面向本科生的操作系统课程,注重动手能力培养,让你在基于 RISC-V 开发的操作系统 xv6 之上增加新特性,深刻理解操作系统的每一部分。

推荐课程 #2:rCoreOS 清华大学的操作系统课程

清华大学是国内首个使用 Rust 进行操作系统教学的高校。目前,陈渝教授和他的学生吴一凡正在编写新的操作系统教材。

这本教程旨在一步一步展示如何 从零开始Rust 语言写一个基于 RISC-V 架构的 类 Unix 内核 。值得注意的是,本项目不仅支持模拟器环境(如 Qemu/terminus 等),还支持在真实硬件平台 Kendryte K210 上运行。

rust 社区也有一个重写操作系统的小项目,可以和这个项目做个对比参照:Writing an OS in Rust,基于 x86架构(the x86 architecture),使用 Rust 语言,编写一个最小化的 64 位内核。

推荐课程 #3: 南京大学计算机系统基础课程的项目 (Programming Assignment)

理解"程序如何在计算机上运行"的根本途径是从"零"开始实现一个完整的计算机系统. PA 将提出 x86/mips32/riscv32 架构相应的教学版子集, 指导学生实现一个经过简化但功能完备的 x86/mips32/riscv32 模拟器NEMU(NJU EMUlator), 最终在NEMU上运行游戏"仙剑奇侠传", 来让学生探究"程序在计算机上运行"的基本原理.

学习自己动手构建一个操作系统,会让你对操作系统有全新的理解。

算法与数据结构

算法(algorithm):是刻画计算过程的机械能行的指令集,它可以在有限步执行指令对给定的一类问题中任一问题求解。

程序 = 数据结构 + 算法

— 尼古拉斯·沃斯, 创建与实现了 Pascal 语言,因这个公式获得 1984 年图灵奖

这部分的知识如何学习和总结比较合适呢?是一个难题。一上来就去啃经典巨著《算法导论》感觉不是很容易,听很多人建议,算法入门不要选算法导论,这个建议或许是中肯的。

普林斯顿大学在 Coursera 上开的公开课 Algorithm 是比较平缓的学习开始:算法,第一部分

找书籍的话 DVP(Sanjoy Dasgupta, Papadimitriou, Umesh Vazirani)的新书 Algorithms 是入门算法不错的选择,UC Berkeley 的 CS170 使用该教材,当然也不能放过手册:《算法设计手册》,这个可以作为参考书使用。

数据结构课程:UCB CS 61B Data Structures

Josh Hug 据说是位“可爱”、人气非常高的讲师,不去了解一下?而且如果你不懂得如何使用栈、队列、树、图等常见数据结构,遇到有难度的问题时,你将束手无策。

Image.png

[双语字幕] [2019 SP/2020 FA] UCB CS 61B Data Structures

学习这部分的目的要有清晰的认知:

了解很多基础的算法和数据结构,学习理解算法设计的精髓,是要学习如何写出[好]的程序,亦涉及你对什么是好代码的判断标准。

题外话:如果你对如何解决本身缺乏思考,这本书说不定会让你茅塞顿开:《怎样解题》

如果是你为了面试准备或程序竞赛学习这部分内容,网络上应该有比较完备的资料,不作过多介绍。

编程竞赛的常见算法

编程竞赛的常见算法(Algorithms for Competitive Programming)

这是一本免费的英文电子书,逐一讲解编程竞赛里面常见的几十种算法,求职面试也用得到。

fasteR

这个仓库是 R 语言学习教程,有大量示例。

其他部分

🔐 密码学和信息安全(可选)

教程资源:

Udacity的公开课《Applied Cryptography》适合快速入门,→ 讲义

书籍资源:

PS:在这里忽略了两个很重要的部分:网络知识和数据库,不用担心,会在后面的部分出现。

机器学习

微软机器学习入门教程: https://github.com/microsoft/ML-For-Beginners

数学的重要性

无论你通过计算做什么,发邮件或转账,在机器内部都有大量的数学运算在进行。

编程活动虽然可以简单理解为人类意图的翻译,但是并不总是自由度很高的一件事,我们需要去遵守语法的组合逻辑,遵循语言的编码规范,按照已制订好的协议标准,使用成熟的调用接口。这就意味着程序中一行行的字符和字母之间的组合方式,存在着严密的逻辑标准。

数理思维(用数学思想解决问题)所培养出的逻辑能力是写出一行好代码的前提。 不过数学是个极其庞杂的体系,在学校阶段(高中/大学/研博阶段)我们都不同程度的接触和学习过部分领域,工作几年,大部分可能都忘光了,重新捡起来看的欲望和难度都非常难。

在具体工作中,从事不同的 IT 岗位,数学的重要程度也不一样。但是比如你想去做一些网络安全/研发操作系统内核/机器学习/人工智能等更深层的工作,理解一些基本的数学概念是必要的。

当然,我们的目标并不是成为一个数学家,如何从工作的角度针对性、持续性的学习就变的非常关键。


计算机科学本身是一个高度交叉的应用学科,其中联系最紧密的就是数学,亦源自数学。

在从计算机视角解决问题的时候,就需要对问题进行抽象,利用数学建模,建立理论基础,进行演绎推理,在理论层面上证明了可行性之后,然后再进行工程实践。

计算机科学中的数学知识 Overview

Image.png

数理逻辑 Mathematics Logic

逻辑之于计算机相当于微积分之于物理(John McCarthy),计算机科学的源起于现代逻辑学的研究发展,也是逻辑学应用最重要的领域,计算机科学领域的发展亦推动着逻辑学的发展。

逻辑学:探讨怎样在前提的合理性与结论的合理性之间(独立于个人意志)有效地、完备地保持一致性可靠性

🔗 如果你对数学和逻辑学历史感兴趣,可以听听中国科学院大学教授冯琦的讲座:冯琦:从莱布尼兹之梦到数理逻辑

莱布尼兹梦想探讨如何用简洁有效的形式来准确表达人类丰富的思想,从而能够将人类的思辨过程和思维演绎的过程行之有效地转换成对形式符号的计算过程,并且计算的结果恰好就是相应的思辨过程和思维演绎过程的结果的形式表达。

从这里开启历经两百多年的逐梦之路:

  • 布尔将逻辑关系用代数来表示 → 布尔代数(布尔逻辑)、1854 年出版《思维规律》;
  • 1883 年,康托发布《一般集合论基础》,将数学放置在一个崭新的基础 🚩;
  • 1879 年,弗瑞格的划时代著作**《概念文字》**,奠定现代数理逻辑;
  • 1889 年,皮阿罗《算术原理-用一种新方法展现》开启数学基础研究先河,将逻辑符号与算术符号区分开,标志关于自然数一阶算术特性的形式表述和内涵最后分离,不再依赖直觉证明;
  • 1898 年,希尔伯特基于”欧几里得几何元素“课程的讲义,出版**《几何基础》,提出了新的几何公理系统,之后为了建立一个坚实牢固的数学基础(公理化),提出了希尔伯特计划**。
  • 1910 年开始,罗素和怀特海合作发表《数学原理》三卷本:罗素悖论 → 数学基础的危机
  • 1931 年哥德尔不完备性定理,他证明了任何一个假定的、能作为数学基础的公理集都不可避免地是不完备的,图灵的停机问题就是一个例子;
  • 1936 年图灵机模型的提出,邱奇-图灵问题对**可计算性理论(Computability theory)**的贡献 🎉;
  • 1940 年冯诺依曼提出存储计算机架构,1946 年,图灵完全的电子计算机 ENIAC 出现;
  • ...

数理逻辑在计算机软硬件设计和实现、编程语言、人工智能理论产生了巨大的影响。数理逻辑课程中所展现的,对计算过程的形式化定义、描述和证明方法,亦被广泛地应用于计算机领域的学术研究中。

从逻辑形式语言的角度理解程序设计语言

用一组基本的指令来编制一个计算机程序,非常类似于从一组公理来构造一个数学证明。

**形式语言(Formal Language)**是为了特定应用而人为设计的语言,有严格的语法规则,与自然进化的语言(比如汉语、英语或法语)不同。例如数学家用的数字和运算符号、化学家用的分子式等。编程语言也是一种形式语言,是专门设计用来表达计算过程的形式语言。 Image.png

上图中,一阶逻辑中很多基础概念:命题逻辑、谓词逻辑,量词以及结构归纳法证明、自由变元、函数、域λ 演算等,都可以在以后编程语言的学习中找到身影,有几个重要的点提及一下:

  • 命题逻辑(Propositional Logic)的本质是逻辑数学化
  • 谓词逻辑(Predicate Logic)的数学本质是引入变量和函数的思想
  • 集合论(Set Theory)是整个数学的基石,本身也是一个代数化的过程,与命题逻辑有很大的相似性,但是有了集合作为基本语言,就可以定义二元关系,比如:
    • 等价关系:意义在于分类,即是数学的基本思想之一,也是数据挖掘的常见任务;
    • 偏序关系:意义在于排序,这个是计算机算法中最基本的研究对象;
    • 函数:有了函数的定义,分析学可以就此展开,而用函数定义二元运算后,代数的基础也准备就绪;
  • 数学证明在数理逻辑中十分重要,而且在自动定理证明和软件开发(如形式验证)有广泛应用;
  • 时序逻辑(Temporal Logic)是分布式原理的理论基础
  • 关系代数(Relational Algebra)是一阶逻辑的分支,是闭合于运算下的关系的集合,在纯数学中是有关于数理逻辑和集合论的代数结构。关系性数据库、SQL 查询语言是我们基本都会学习的基础知识。

还有个非常有意思的概念,在学习编程的过程中肯定会接触**“间接寻址和指针”的知识,这个技术的产生和原理可以从哥德尔编码方法(一种递归算法)的思想中**找到出处,有兴趣可以自行检索扩展阅读。

离散数学(Discrete Math)

作为计算机学科工具,离散建模是离散数学区别高等数学的根本之处,也是离散数学与计算机紧密关联之处,也是使离散数学成为计算机专业核心课程的原因之一。

-- 徐洁磐,南京大学教授

离散数学的主要研究对象是离散对象,不是连续变化的,而是拥有不等、分立的值。类似下面的图这样,是离散数学的研究对象之一,它们拥有有趣的数学性质,可以作为现实世界用来解决问题的模型,在计算机算法设计中有着举足轻重的作用。

Image

上面讲的数理逻辑也属于离散数学的一部分,我们看看看其他部分的内容的意义和应用:

**图论:**图论是研究网络的数学分支,常被认为包含于组合数学中,但这一分支已经发展得足够庞大和有特点,并有自身领域所研究的问题,因此被视为一个独立的主题,在数学和科学的所有领域都有广泛的应用。例如:有名的七桥问题。

抽象代数:代数结构****既可以是离散的,也可以是连续的。离散代数包括逻辑门和编程中使用的逻辑代数数据库中使用的关系代数代数编码理论中重要的离散有限、环和形式语言理论中的离散半群幺半群

**形式语言理论研究和自动机(Automata Theory):是人工智能领域中自然语言处理(NLP)**的理论基础知识,我们平常比较熟悉的场景:单词拼写检查(有限自动机的解决方案)。

线性代数 Linear algebra

怎么说,矩阵知识无疑是非常重要,是每个工程师都需要掌握的数学知识。

拿两个实际开发过程中例子 🌰 来讲:

  • 假如我们以一个巨大的有向邻接矩阵表示微博用户中任意两个好友之间的关注情况,我们则可以计算一些信息:中心度、相互影响度、相似度,得到这些信息后,我们就可以基本判断用户的基本情况,进行标签和用户画像;
  • 计算机图形学中也可以使用矩阵计算来让 GPU 高效的计算出物体的目标位置。

不过作为大学期间线性代数挂科的学渣,不好给大家推进如何学习,不过想要了解线代的本质和快速温习相关概念,3Blue1Brown(深入浅出、直观明了地分享数学之美) 的中英双语视频是绝佳选择,如果同时能唤起你对其他学科的兴趣,那就更棒了。

之前看一些招聘岗位的能力要求,有一条是这样的, 温习过这个系列的视频后,你应该会胸有成竹。

You should be able to understand linear algebra transformations in 3d space.

在机器学习中的重要性

线性代数、概率论与统计学是机器学习的基础工具,从这个角度出发,学习线性代数的一些建议:

一方面,紧紧围绕空间变换这个线性代数的主要脉络,从坐标与变换、空间与映射、近似与拟合、相似与特征、降维与压缩这五个维度,环环相扣的展开线性代数与机器学习算法紧密结合的最核心内容,深刻理解如何用空间表示数据、用空间处理数据、用空间优化数据,用一条线索拎其整个学科主干内容。

另一方面,结合机器学习中的典型实战案例,面向应用将线性代数这一数学工具用熟用好,同时以Python语言为工具进行数学思想和解决方案的有效实践,无缝对接工程应用。

— 知乎用户

相关课程推荐:

概率论与统计学 Probability & Statistics

概率论与统计学是数学一个极其重要的分支,它研究偶然现象和随机现象的内在规律性,以及如何从数据中提取有意义的信息。这两门学科的应用也正迅速成为我们生活的基础,不仅局限于计算机科学,自然科学领域、经济学领域都在大量采用概览统计方案,理解概率亦有助于做好人生决策。

有些基本概念要了解:概率分布、期望、独立性、条件期望和马尔可夫链,以及极大似然估计、置信区间和假设检验。

  • 想过滤垃圾邮件,不具备概率论中的贝叶斯思维恐怕不行;
  • 想试着进行一段语音识别,则必须要理解随机过程中的隐马尔科夫模型
  • 想通过观察到的样本推断出某类对象的总体特征,估计理论和大数定理的思想必须建立;
  • 在统计推断过程中,要理解广泛采用的近似采样方法,蒙特卡洛方法以及马尔科夫过程的稳态也得好好琢磨;
  • 想从文本中提取出我们想要的名称实体,概率图模型也得好好了解。

同样,3Blue1Brown 的双语视频依旧是不错的开始:

一点学习建议:

从兴趣出发,抽丝剥茧,通过实践应用,逐步积累补充知识体系比强迫自己学一些课程更有效。 关键在于能下日日不断之功,滴水穿石。

如果你对数学没有什么兴趣,读本跟计算机行业比较近的科普书亦是不错的选择:数学之美 - 吴军

想看更多...

这里简单收集整理了一些资料,并不能覆盖全部,但是不用着急,有前辈做了这个工作,如果你想看看学习计算机科学总共需要多少数学基础,翻翻宾夕法尼亚大学计算机和信息科学系教授 Jean Gallier (72 岁)的开源书籍:

  • 《Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Engineering》,有 1900 页。

🔗 下载地址: https://www.cis.upenn.edu/~jean/math-deep.pdf

这本书目的是介绍线性代数和最优化理论的基础知识以及这些知识在机器学习、机器人学、计算机视觉等领域的应用。

Image Jean Gallier


计算机科学中的数学

麻省理工学院计算机科学与工程专业本科生的初等离散数学课程讲义,涵盖了国外计算机科学专业涉及的基础数学知识,内容涉及形式逻辑符号、数学证明、归纳、集合与关系、图论基础、排列与组合、计数原理、离散概率、递归等,特别强调数学定义、证明及其应用方法。


油管上面也有很多自学数学的视频:https://www.youtube.com/watch?v=fb_v5Bc8PSk

以上,祝你可以顺利开启学习数学基础的大门。

参考资料

黑客精神

ESR

这篇文章是对 ESR 的经典文章 How to become a hacker ? Revision 1.52 - 2020.01.03 的最新修订翻译,某种意义上来讲,这不仅仅是一篇黑客入门的读物,也是不错的编程入门指导。

什么是黑客?

对 “黑客” 术语的定义,大部分定义都涉及高超的编程技术,解决问题和克服限制的乐趣。如果你想知道如何成为一名黑客,只有两方面是非常重要的(态度和技术)。

长久以来,存在一个专家级程序员和网络高手的共享文化社群,其历史可以追溯到几十年前第一台分时共享的小型机和最早的 ARPAnet 实验时期。 这个社群文化的成员创造了 “Hacker/黑客” 这个术语。 黑客们建起了 Internet,使 Unix 操作系统 成为今天这个样子。黑客们让 www(万维网) 正常运转。如果你是这个文化的一部分,如果你已经为它作了些贡献,而且圈内的其他人也知道你是谁并称你为一个黑客,那么你就是一名黑客。

黑客精神并不仅仅局限于软件黑客文化圈中。有很多人同样以黑客态度对待其它事情如电子器件和音乐,事实上,你可以在任何较高级别的科学和艺术中发现它。软件黑客们识别出这些在其他领域同类并把他们也称作黑客 -- 有人宣称黑客实际上是独立于他们工作领域的。 但在本文中,我们将注意力集中在软件黑客的技术和态度,以及发明了 “黑客” 一词的_分享文化传统_。

另外还有一群人,他们大声嚷嚷着自己是黑客,实际上他们却不是。他们是一些蓄意破坏计算机和电话系统的人(多数是青春期的少年)。真正的黑客把这些人叫做 “Cracker/骇客”,并不屑与之为伍。

多数真正的黑客认为骇客们是些不负责任的懒家伙,还没什么大本事。专门以破坏别人安全为目的的行为并不能使你成为一名黑客, 正如拿根铁丝能打开汽车并不能使你成为一个汽车工程师。不幸的是,很多记者和作家往往错把“骇客”当成黑客;这种做法会激怒真正的黑客。

两者根本的区别: 黑客们创造,骇客们破坏 。

如果你想成为一名黑客,继续读下去。如果你想成为一名骇客。去读 alt.2600 新闻组[译者:一个关于网络安全的新闻组],并且在发现你并不是你想的那么聪明的时候去蹲 5-10 次监狱。关于骇客,我只想说这么多。

黑客的态度

黑客们解决问题、创造事物,信仰自由以及互相帮助。要想被认为是一名黑客,你的行为必须表现出已经具有了这种态度。但是要想做的好像具备这种态度,你就不得不真的拥有这种态度。

但是如果想通过培养这种黑客态度在黑客文化中得到认可,那么你就大错特错了。变成具备这种态度的人对你来说才非常重要,帮助你更好的学习,并给你提供源源不断的动力。同所有创造性艺术一样,成为大师最高效的方式就是模仿大师的:不是仅从理智上,更要从精神上进行模仿

或者就像这首现代禅意诗描述的:

To follow the path:
look to the master,
follow the master,
walk with the master,
see through the master,
become the master.

所以,如果你想成为一名黑客,重复以下事情直到你相信他们:

1. 这个世界充满了待解决的迷人问题

做一个黑客有很多乐趣,但是需要颇费气力才能获得这些乐趣。这些动力需要动机。卓越的运动员从强健体魄、挑战自我身体极限中汲取动力。同样的,作为黑客,你必可以从解决问题、磨练技术、锻炼智力中获得快感。

如果你不是这样的人又想做黑客,你就要设法成为这样的人。否则,你会你发现你的黑客热情会被其他诱惑无情的吞噬,比如:性、金钱、社会上的虚名等等。

你也必须对自己的学习能力建立信心:你要相信尽管你现在所知甚少,但是随如果你一点一点的学习、试探、实践,你最会掌握它

2. 一个问题都不应该被解决两次

创造性的大脑是宝贵的、有限的资源。在这个世界上还存在着如此多的迷人的、有趣的问题的时候,它们不应该被浪费去重复发明轮子。

作为一个黑客,你必须相信其他黑客的时间是宝贵的:因此分享信息,解决问题并发布结果给其他黑客几乎是一种道义,这样其他人就可以去解决新问题而不是重复地对付旧问题。

需要注意的是, 一个问题不应该被解决两次并不意味着已存在的解决方案是真理,或者是唯一的正确解决方案。通常,在发现一个解决方案之前,我们需要会学习大量和问题相关的知识。如果这个解决方案没问题,通常来讲,也需要来决策一下能不能做的更好。解决方案也有可能存在人为的技术障碍、法律或者机构保护的障碍(比如闭源软件)阻止人们去重用,强制人们去重新发明轮子。

(你不必认为你一定要把你的发明创造公布出去,但这样做的黑客是赢得大家尊敬最多的人。卖些钱来给自己养家糊口,买房买车买计算机甚至发大财和黑客价值也是相容的,只要你别忘记你的忠诚,你的创造性艺术,你的黑客朋友们正在做的事情)

3. 无趣和乏味的工作是犯罪

黑客们(以及具有创造力的人们)从来不会被愚蠢的重复性工作所困扰,因为当这种事情发生的时候就意味着他们没有在做只有他们能做的事情:解决新问题,这样的浪费对每一个人都是伤害。因此,无趣和乏味的工作不仅仅是不舒服而已,而是极大的犯罪。

作为黑客,你必须完全相信这一点并尽可能把乏味的工作自动化 ,不仅仅是为了自己,也为了其他人(特别是其他黑客们)。

(对此有个明显的例外,就是黑客们有时会重复性的枯燥工作来进行脑力休息,或者是为了获的一些技巧以及除此之外无法获得的经验。但是这是他/她自己的选择,有脑子的人不应该被迫做无聊的活儿。)

4. 追求自由

黑客是天生的反权威主义者。任何能向你发命令的人会迫使你停止解决令你着迷的问题,同时,按照权威的一般思路,他通常会给出一些极其愚昧的理由。因此,不论何时何地,任何权威,只要他压迫你或其他黑客,就要和他斗到底。

(这并非说任何权力都不必要。儿童需要监护,罪犯也要被看管起来。 如果服从命令得到某种东西比起其他方式得到它更节约时间,黑客会同意接受某种形式的权威。但这是一个有限的、特意的交易;权力想要的那种个人服从不是你的给予,而是无条件的服从。)

权力喜爱审查和保密。他们不信任自愿的合作和信息分享,他们只喜欢由他们控制的合作。因此,要想做的像个黑客,你得对审查、保密,以及使用武力或欺骗去压迫人们的做法有一种本能的反感和敌意。

5. 态度不能代替能力

要做一名黑客,你必须培养起这些态度。但只具备这些态度并不能使你成为一名黑客,就像这并不能使你成为一个运动健将和摇滚明星一样。

成为一名黑客需要花费智力,实践,贡献和辛苦。

因此,你必须学会拥有质疑精神,并尊重各种各样的能力。黑客们不会为那些故意装模做样的人浪费时间,但他们却非常尊重能力 -- 尤其是黑客层面的能力,但在任何方面的能力都很重要。具备很少人才能掌握的技术方面的能力尤其为好,而具备那些涉及脑力、技巧和聚精会神专注的能力为最佳。

如果你推崇能力,你会享受能力提升带来的乐趣:辛苦的工作和贡献会成为一种高度娱乐而非乏味。要想成为一名黑客,这一点很重要。

基本的黑客技巧

黑客精神固然至关重要,但是技术必不可少, 态度无法替代技术 。在被其他黑客成为黑客之前,有一些基本的技术你必须掌握。

这套基本技术随着新技术的出现和老技术的过时也随时间在缓慢改变。例如,过去包括使用机器码编程,而知道最近才包括了 HTML 语言。但现在清晰包括以下方面:

1. 学习如何编程

这当然是最基本的黑客技术。如果你还不会任何计算机语言,我建议你从 Python 开始。它设计清晰,文档齐全,对初学者很合适。尽管是一门很好的初级语言,它不仅仅只是个玩具。它非常强大,灵活,也适合做大型项目。我写了一篇详细的文章去 评估 Python (链接失效)。在 Python 网站上也有不错的 教程, 在 Computer Science Circles 上还有很多不错的文章。

我过去常常推荐将 Java 作为早期学习的一门语言。但是这篇 评论 改变了我的想法(搜索 “The Pitfalls of Java as a First Programming Language”, 点进去读一下相关的信息)。作为黑客,不会接受解决问题仅仅像五金店里面的管道工一样[译者:只是把一大堆硬件组装起来],你必须知道每个组件是如何运作的。现在我认为最好的方式是先学习 C 和 LISP ,然后再学习 Java 。

也许这里有个一般性的问题。如果一门语言为你做了太多,它可能对开发是一个称手的工具同时却很难去拿来学习。不仅仅是语言存在这个问题,Web 应用程序框架,比如 RubyOnRails , CakePHP , Dingo 或许使你很容易到达一个浅显的认识,但是当你碰到一个难题而没有这些框架资源支持的时候你就会束手无策,甚至只是去调试一个简单问题的解决方案。

比 Java 更好的选择是学习 Go 语言, 很容易从 Python 语言迁移过来,学习 Go 会给予你很多的助力,如果你的下一计划是学习 C 语言。此外,未来 Go 可能会在一定程度上取代 C 成为系统级编程语言,这种情况估计会发生在很多传统 C 语言的应用范畴。

译者:2022 年这个时间点,Rust 语言也是一个不错的开始选项。

如果你要做一些严肃的编程工作(比如操作系统内核开发),你就必须要学会 C,Unix 的核心语言。 C++ 和 C 有很密切的关系,如果你知道其中一个,学习另外一个不用耗费太多精力。无论哪一个语言你都可以先尝试开始学习一下,但是,实际上,尽量避免直接用 C 编程使你会更加具有生产力。

C 非常高效,很节省你的机器资源。不幸的是,C 需要你手动做大量的低层次的内存管理来获得高效。低级编码非常复杂、容易出错,会花费你大量的时间在调试程序上面。由于今天的机器性能非常强大,还这样做的话通常来讲是一个糟糕的决定,更加明智的是去使用一门机器时间低效的的语言,但是你的编程时间更高效。因此,选 Python。

其他对黑客而言比较重要的语言包括 Perl 和 LISP。 Perl 很实用,值得一学;它被广泛用于活动网页和系统管理,因此即便你从不用 Perl 写程序,至少也应该能读懂它。对于许多使用 Perl 的人,我建议学习使用 Python,尽量去避免在那些不需要 C 的机器效率的工作中使用 C 语言编程,你只需要能够读懂那些代码就可以。

LISP 值得学习是因为当你最终掌握了它你会得到丰富的经验;这些经验使你在以后的日子里成为一个更好的程序员,即使你实际上可能很少使用 LISP 本身。(你可以通过使用 Emacs 编辑器来获得 LISP 经验,或者 GIMP 的 Script-Fu 插件。)

当然,实际上你最好五种都会。 (Python, C/C++, Perl, and LISP). 除了是最重要的黑客编程语言,它们还代表了非常不同的编程方法,每种都会让你受益非浅。Go 还没有到可以被纳入最重要的黑客语言的程度,但它似乎正朝着这个方向发展。

但是你需要意识到仅仅去学会几种语言是不会达到黑客需要的技术水平,甚至也不能成为一个程序员。你需要站在通用性的角度思考如何编写代码,独立于任何编程语言。

要想成为一个真正的黑客,你要达到的标准是:可以通过相关的手册和你已经知道的知识在数天内学习一门新语言,这意味你会学习到几种非常不同的语言。

这里我无法完整地教会你如何编程,这是个复杂的活儿。但我可以告诉你, 书本和课程也无济于事 。几乎所有最好的黑客都是自学成材的。真正能起作用的就是去亲自读代码和写代码

彼得•诺维格,谷歌的顶尖黑客,也是现在被广泛使用的人工智能领域的教科书的共同作者,写了一篇棒极了的文章 Teach Yourself Programming in Ten Years。他的成功编程秘诀值得关注。

学习如何编程就象学习用自然语言写作一样。最好的做法是读一些大师的名著,试着自己写点东西,再读些,再写点,又读些,又写点....如此往复,直到你达到自己在范文中看到的简洁和力量

在如何学习编程中,我已经说了很多次这个学习过程。只是一个很简单的教导,但却是最难的事情。

过去找到好的代码去读是困难的,因为很少有大型程序的可用源代码能让新手练手。这种状况已经得到了很大的改善;现在有很多可用的开放源码软件,编程工具和操作系统(全都有黑客写成),这使我们自然地来到第二个话题。

2. 获取一个开放源码的 Unix 并学会使用、运行它

我假设你已经拥有了一台个人计算机或者有一个可用的( 今天的孩子们真幸福 :-) )。新手们最基本的一步就是得到一份 Linux 或 BSD-Unix,安装在个人计算机上,并运行它。

当然,这世界上除了 Unix 还有其他操作系统。但它们都是以二进制形式发送的:你无法读到它的源码,更不可能修改它。尝试在 DOS 或 Windows 的机器上学习黑客技术,就象是在腿上绑了铁块去学跳舞。

使用 Mac OS X 当然也可以,但是只用一部分系统式开放源代码的–你可能会遇到很多限制,而且还必须小心不要养成只开发苹果专用代码的坏习惯。如果你集中精力在外壳之下的 Unix,你会学到一些有用的东西。

除此之外,Unix 还是 Internet 的操作系统。你可以不知道 Unix 而学会用 Internet,但不懂它你就无法成为一名Internet 黑客。因为这个原因,今天的黑客文化在很大程度上是以 Unix 为中心的。(这点并不总是真的,一些很早的黑客对此很不高兴,但 Unix 和 Internet 之间的共生关系已是如此之强,甚至连微软也无可奈何)

所以,装一个 Unix – 我个人喜欢 Linux,不过也有其他选择。(你也可以在同一台机器上同时运行 DOS, Windows 和 Linux)学会它。运行它。用它跟 Internet 对话。读它的代码。试着去修改他。你会得到比微软操作系统上好的多的编程工具(包括 C, Lisp, Python, and Perl),你会得到乐趣,并将学到比你想象的更多知识。

了解更多 Unix 的知识,可以查看 Loginataka。你或许还想看一下这本书 《Unix 编程艺术》

Let's Go Larval! 博客是一个 Linux 新手的学习记录,我觉得写的很清晰明了,对其他人也很有帮助。How I Learned Linux 是学习 Linux 一个不错的起点。

如何获得一个 Linux, 查看 Linux Online 站点,你可以从这个站点下载,也可以寻找一个本地的 Linux 用户组来帮助你安装。

译者:推荐 ArchLinux 发行版作为起点 入手

在这篇教程文章头十年的时光里,我从一个初学者的角度来看,所有的 Linux 发行版几乎是相同的。但是在 2006-2007,一个更好的选择出现了, Ubuntu。 当然,其他的 Linux 发行版有自己领域的优势, Ubuntu 遥遥领先的是对新手用户的易用性。需要注意的是,Ubuntu 在几年后推出的默认界面 Unity 比较丑陋和不可重用,Xubuntu 或者 Kubuntu 变体或许比较好。

你可以再 www.bsd.org 找到 BSD Unix 的帮助信息和资源;

还有一种试水的方式是启动使用 Linux 爱好者所说的 LiveCD,他从 CD 可以运行一个 Linux 发行版,而不用担心去更改硬盘布局。这将会运行比较缓慢,因为 CD 非常缓慢,但是这是一种可以快速看一下内容而不会造成任何问题。

我已经写了一些关于 Unix 和 Internet 的 启蒙内容

我常推荐新手独立安装 Linux 或者 BSD 系统。现在的安装程序已经足够好,你可以完全自己动手搞定,即使是新手。尽管如此,特推荐你咨询或者搜索本地的 Linux 用户组去寻求帮助,这不会有任何损失,安装过程也会更顺利。

3. 学习如何使用互联网以及写 HTML 语言

大多黑客文化建造的东西都在你看不见的地方发挥着作用,帮助工厂、办公室和大学正常运转,表面上很难看到它对他人的生活的影响。Web 是一个大大的例外。即便政客也同意,这个巨大而耀眼的黑客玩具正在改变整个世界。单是这个原因(还有许多其它的), 你就需要学习如何掌握 Web。

这并不是仅仅意味着如何使用浏览器(谁都会),而是要学会如何写 HTML,Web 的标记语言。如果你不会编程,写HTML会教你一些有助于学习的思考习惯。因此,先建起自己的主页。

但仅仅建一个主页也不能使你成为一名黑客。 Web里充满了各种网页。多数是无意义的,零信息量垃圾–非常炫酷的垃圾,请注意,垃圾都是一样的。(更多可以查看 HTML Hell

要想有价值,你的网页必须有内容–必须有趣或对其它黑客有用。这样,我们来到下一个话题...

4. 你过你还不熟悉实用英语,学会它

作为一个美国人和英语为母语的人,之前我并不情愿去建议这个。但是避免被扣上文化帝国主义的名头,还是把它提出来。而且还有几个其他语种为母语的人力劝我指出英语是黑客文化和 Internet 的官方语言,和你需要知道它在黑客社区里的功能。

在 1991 年我了解到许多黑客把英语当做第二语言,并在技术讨论中使用它甚至他们使用同一母语。有人告诉我这是因为英语比其他语言有更丰富的专业词汇,对于工作来说是一个更好的工具。同样的原因,英文科技书籍的翻译版本通常不会让人很满意(当他们全部完成)。

Linus Torvalds,芬兰人,用英语写代码注释(他显然没有想过用其他方式)。他流利的英语是他有能力去招募一个全球性的 Linux 开发社区的重要因素。这是一个值得学习的榜样。

成为一个英语为母语的人,并不能保证你的语言技巧足够实用到做一个黑客。如果你的写作是半文盲、不合语法,并且充斥着拼写错误,许多黑客(包括我)往往会直接无视你。虽然不拘小节的写作不一定意味着马虎的思考。但是我们通常发现这两者有很强的相关性–我们没有马虎的思考家。如果你还不能胜任,开始学习。

黑客文化中的地位

与大部分不涉及金钱的文化一样,黑客世界的运转靠声誉维护。你设法解决有趣的问题,但它们到底多有趣,你的解法有多好,是要有那些和你具有同样技术水平的人或比你更牛的人去评判的。

相应地,当你在玩黑客游戏时,你知道,你的分数要靠其他黑客对你的技术的评估给出。(这就是为什么只有在其它黑客称你为黑客是,你才算得上是一名黑客)这个事实常会被黑客是一项孤独的工作这一印象所减弱;它也会被另一个黑客文化的禁忌所减弱(此禁忌的效力正在减弱但仍很强大):拒绝承认自我或外部评估是一个人的动力。

特别地,黑客世界被人类学家们称为一种精英文化。在这里你不是凭借你对别人的统治来建立地位和名望,也不是靠美貌,或拥有其他人想要的东西,而是靠你的贡献,尤其是奉献你的时间,你的才智和你的技术成果。

要获得其他黑客的尊敬,你可以做以下五种事情:

1. 写开放源码的软件

第一个(也是最基本和传统的)是写些被其他黑客认为有趣或有用的程序,并把程序的原代码公布给大家共享。

过去我们称之为自由软件-free software,但这却使很多不知 free 的精确含义的人感到不解,现在我们很多人使用-开放源码 Open Source 这个术语。

黑客世界里最受尊敬的大牛们是那些写了大型的、具有广泛用途的软件,并把它们公布出去,使每人都在使用他的软件的人。

但是这也有一个历史转折点,在 90 年代中期之前,黑客们很尊敬他们中间的开源软件开发者,那是大多数黑客都在为闭源软件工作。当我在 1996 年写这份文件的第一个版本的时候,依然如此。开源软件的主流化是从 1997 年开始的。今天,黑客社区和开源软件开发者社区是两个描述和本质上基本相同的文化和人群,但是需要记住的是,这并非总是如此。(更多信息,查看 Historical Note: Hacking, Open Source, and Free Software 部分

2. 帮助测试并修改开放源码的软件

黑客们也尊敬也那些使用、测试开放源码软件的人。在这个并非完美的世界上,我们不可避免地要花大量软件开发的时间在测试和抓臭虫阶段。 这就是为什么任何开放源码的作者稍加思考后都会告诉你好的 beta 测试员象红宝石一样珍贵。 (他们知道如何清楚描述出错症状,很好地定位错误,能忍受快速发布的软件中的 bug,愿意使用一些简单的诊断工具) ,调试阶段是一场旷日持久、精疲力尽的噩梦还是一件有趣的麻烦事取决于他们。

如果你是个新手,试着找一个感兴趣、正在开发的程序,做一个好的 beta 测试员。从帮着测试,到帮着抓 bug,到最后帮着修改程序,你会不断进步。以后你写程序时,会有别人来帮你,你就得到了你当初善举的回报。

3. 公布有用的信息

另一个好事是收集整理网页上有用有趣的信息或文档如 FAQ。许多主要 FAQ 的维护者和其他开放源码的作者一样受到大家的尊敬。

4. 帮助维护基础设施的运转

黑客文化是靠志愿者运转的。要使 Internet 能正常工作,就要有大量枯燥的工作不得不去完成,管理 mail list,newsgroup,维护大量文档,开发 RFC 和其它技术标准等等。

做这类事情的人会得到很多人的尊敬,因为每人都知道这些事情是耗时耗力的苦役,不象编码那样好玩。

做这些事情需要毅力。

5. 为黑客文化本身服务

最后,你可以为这个文化本身服务(例如象我这样,写一个“如何成为黑客”的初级教程 :-)(也可以像我这样搭建一个优质的中文黑客社区 :-) ) 这并非一定要在你已经在这里呆了很久,精通所有以上 4 点,获得一定声誉后后才能去做。

黑客文化没有领袖。精确地说,它确实有些文化英雄和部落长者和历史学家和发言人。若你在这圈内呆的够长,你或许成为其中之一。

记住:黑客们不相信他们的部落长者的自夸的炫耀,因此很明显地去追求这种名誉是危险的。你必须具备基本的谦虚和优雅。

黑客和怪人 Nerd 的联系

同流行的传说相反,做一名黑客并不一定要你是个怪人。然而,很多黑客都是怪人。 做一个出世者有助于你集中精力进行更重要的事情,如思考和编程

因此,很多黑客都愿意接受“怪人”这个标签,更有甚者愿意使用 极客/geek 一词并自以为豪 --- 这是宣布他们与主流社会不合作的声明(还有对科幻小说和战略游戏的狂热也常常是黑客的标签)。术语 书呆子/nerd 最早被使用是在 1990s,那时,书呆子/nerd 是一个温和的贬义词,而 geek 更加刺耳。在 2000 年之后这些术语开始变化,至少在美国流行文化中,在那些非技术人员当中也有极客文化的骄傲。

如果你能集中足够的精力来做好黑客同时还能有正常的生活,这很好。今天作到这一点比我在1970年代是个新手是要容易的多。今天主流文化对技术怪人要友善的多。甚至有更多的人意识到黑客通常更富爱心,是块很好的做恋人和配偶的材料。

如果你因为生活上不如意而为做黑客而吸引,那也没什么 --- 至少你不会分神了。或许以后你会找到自己的另一半。

风格的指引

重申一下,做一名黑客,你必须进入黑客精神之中。当你不在计算机边上时,你仍然有很多事情可做。它们并不能替代真正的编程(没有什么能替代编程),但很多黑客都那么做,并感到它们与黑客精神存在一种本质的关联。

  • 学会如何用你的母语写作。尽管传统观念上程序员并不需要写文章,包括我所知道所有最棒的黑客都非常擅长写作。

  • 阅读科幻小说。参加科幻小说讨论会。(一个很好的寻找黑客的场合)

  • 加入一个黑客空间、社区去创造一些东西。(另外一个很好的方式去寻找黑客和潜在黑客)。

  • 尝试一种武术形式。那种需要精神自律的武术形式在某些重要的方面和黑客做的方式是一样的。在黑客中最流行武术形式肯定是亚洲的空手艺术,比如跆拳道,各种形式的空手道、功夫、合气道或者柔术。西方剑术和亚洲剑术也有明显的追随者。在一些法律许可的地方,手枪射击在 1990s 后也人气高涨。最黑客风格的武术形式是那些强调精神自律、放松意识、精确控制的,而不是原始的力量,运动能力或物理韧性。

  • 研究冥想和禅宗。

  • 练就一双精确的耳朵,学会鉴赏特别的音乐。学会玩某种乐器,或唱歌。

  • 培养你对双关语和文字游戏的欣赏。

这些事情,你做的越多,你就越适合做黑客。至于为什么偏偏是这些事情,原因并不很清楚,但它们都涉及到了左-右脑的综合技巧,这似乎是关键所在。(黑客们既需要清晰的逻辑思维,有时也需要强烈的跳出逻辑之外的直觉)

译注 : Work as intensely as you play and play as intensely as you work. [译:不会翻译,待处理]

对于真正的黑客,娱乐、工作、科学、艺术的界限逐渐消失,或者被合并到了高层次的创造性的有趣活动。而且,也不会满足于很窄的技术领域。因此许多黑客自我描述我程序员,他们可能胜任多种相关的技能–系统管理员,网页设计,修电脑是最常见的。一个黑客是系统管理员,在另一方面,很可能拥有相当熟练脚本编程和网站设计。黑客们不做半途而废的事情,对于他们所有投资的技巧来说,他们往往会非常擅长。

最后,还有一些不要去做的事情:

  • 不要使用愚蠢的,过于哗众取宠的 ID。
  • 不要再论坛或者新闻组挑起战争的火焰(或者在其他任何地方)。
  • 不要自称为网络崩客(punk) ,也不要对那些人浪费时间。
  • 不要寄出充满拼写和语法错误的 email,或张贴错误百出的文章。

做以上的事情,会损害你的声誉。黑客们个个记忆超群 – 你将需要数年的时间让他们忘记你的愚蠢。

网名和虚拟身份问题应该得到一定的关注。将你的 ID 隐藏在一个虚拟身份之下的做法很 low,是骇客, warez d00dz 的做法,更是年少和愚蠢的特征。黑客不这样做,他们为他们的所作所为感到自豪,并希望用自己的名字关联。所以如果你有一个虚拟身份,将其删除,在黑客文化中这只会标志着你是一个失败者。

其他资源

为什么要掌握 Linux

熟悉 Linux 环境有几个显而易见的好处:

  1. 加深对操作系统/IO处理/网络编程的理解,对编程和程序架构都有非常大的助力;
  2. 让 Linux 成为你强力的工作助手,提升工作效率和解决问题的能力;
  3. 熟悉 Linux 也是很多软件岗位的必备技能,更是某些岗位的加分项目;

一旦选择成为软件工程师,Linux 和命令行环境都会伴随着你的职业生涯,甚至比职业生涯还长久,你会喜欢上这些的。

如果你在这个方面走的更深入一些,有良好的系统编程能力,职业选择亦会宽广许多。

从什么地方开始

亲自动手装一台 Linux 个人开发机器是最佳的开始,不建议你在个人办公设备或者虚拟机中尝试。这个过程痛苦和乐趣并存,并将学到比你想象中要多的知识,最关键的是不会再恐惧

如果你所在的处境网络状况非常不错,可以考虑从云服务商采购基础的入门云服务器(放在网上的虚拟计算机,通过 ssh 远程连接)来学习,对学生党来说,攒一台树莓派或开发版来折腾更好。

安装 ArchLinux 作为个人开发环境

ArchLinux 是一个轻量级、灵活、社区维护的 Linux® 发行版本,它足够简单且与时俱进,商业诉求和历史包袱很少,社区文档非常清晰(中文翻译覆盖率高),是非常好的学习资料。

第一步的目标很简单,就是在你的设备安装 ArchLinux 操作系统,这个过程需要搞定的事情:

Screen Shot 2022-03-12 at 15.16.02.png 折腾的目标:

  1. 学会使用镜像安装操作系统,理解基本的计算机硬件知识,磁盘格式/分区规划/启动等等;
  2. 理解操作系统启动的整个流程:硬件探测、引导加载、内核加载、systemd init 流程等;
  3. 学习一些非常基础的终端命令和 Shell 基础,至少达到能在命令行操作的程度;
  4. 了解窗口管理器及桌面环境的发展,自己搞定显卡驱动、选择一个桌面环境进行安装、配置;
  5. 搞定网络连接,网卡配置、防火墙、DNS,某些情况可以考虑增加网络代理的设置;
  6. 学习用户与权限的基础概念,用 sudo 创建一个管理员账户,学会使用 ssh 远程登录计算机;
  7. 学会如何在命令行管理文件,就行用鼠标和界面来操作那样;
  8. 学会如何进行软件包安装和更新卸载,包括服务的管理;
  9. 日常维护的操作:错误定位、漏洞修复、数据备份等等;

在你拥有了一个基础的 Linux 环境之后,就可以根据后续的学习计划,自行配置完整的开发环境。

必备的基础知识

📌 新手提示

Linux 终端的基本命令(ls、cp、rm 等等)都是 GNU coreutils 工具包提供的,这个网站是对该工具包的详细介绍,逐一分析其中近100个工具的内部实现。

Image

Decoded: GNU coreutils – MaiZure's Projects

1)命令行文本编辑器 Vim or Emacs

开发者的工作基本都是在文本编辑器下进行的,在命令行下有些编辑器的基础你需要了解一些,并不是说你要掌握所有的编辑器使用,不过我非常建议你在自己的设备上面安装 Vim 和 Emacs,不用看任何教程,花一两个小时熟悉一下这两个编辑器自带的教程是必要的,软件工具层面有非常多的争吵和论战,至于你未来站在那一边,与你自己的喜好和工作环境会有很大关系。

在桌面环境下也有非常不错的免费、强大的编辑器(IDE):

  • Sublime Text
  • Visual Studio Code
  • Atom
  • Notepad++ etc.

2) 网络编程 Network Programming

现在在地球上有无数台个人计算机和移动设备,这个设备如何通过网络连接在一起达到资源共享和通信的目的,程序之间的数据如何传输?即使你未来并不会做相关的工作,理解清楚这些理论知识也是必须的。必须要掌握的知识点:

  • Linux 中进程和线程概念,多线程编程实践;

Life and Death of a Linux Process

  • 网络分层模型和 TCP&IP 协议栈,应用层协议;
  • 套接字编程实践(高级套接字);
  • IO 模型 (poll(), select(), signal-driven I/O, epoll);
  • 与网络相关的命令和软件工具使用(:netstat and tcpdump);

如果你对网络层及应用层非常感兴趣,可以拓展了解一下:

[19] Linux 命名管道简介: https://goodyduru.github.io/os/2023/09/26/ipc-named-pipes.html [20] 《套接字》: https://goodyduru.github.io/os/2023/10/03/ipc-unix-domain-sockets.html [21] 《Unix 信号》: https://goodyduru.github.io/os/2023/10/05/ipc-unix-signals.html

3)Linux Shell 脚本能力

有一定的 BashPython 编程能力会让你能解决很多工作中的自动化或者数据处理问题,包括解决问题的方式和途径也会增加不少。

有很多强大的文本处理命令可以在工作过程中遇到了,再逐步扩充学习。

接触到这步,你肯定也会学习不少其他 shell 的使用,比如 zsh,用它来打造称心的终端环境。

进阶知识

1)容器、Docker 与 k8s

不必慌张,这些概念并没有听起来那么吓人,你只需要多花点时间了解一些概念,这些技术也更偏运维和部署发布的环境。

极客时间有个付费课程把这块讲的非常清晰,你可以通过这个课程学习(非广告):

深入剖析Kubernetes_容器_K8s-极客时间

2)服务器安全和监控

运维方向需要熟悉很多企业级服务的部署和维护(现场环境往往更复杂,集群灾备,混合云等等),以及安全和监控(zibbix、prometheus)领域方向的必备知识。

后续补充这块的内容。

3)设备驱动开发、嵌入式开发方向

不太熟悉,感兴趣的同学可以自行搜索。


最关键的是:

持续的好奇心,废寝忘食的学习、折腾精神必不可少。

https://learn.microsoft.com/en-us/linux/

为什么用 Archlinux 作为起点?

ArchLinux 是一个轻量、灵活、社区维护的 Linux® 发行版本,它足够简单且与时俱进,商业诉求和历史包袱很少。

  • Arch Linux 基于 x86-64 架构的一类 GNU/Linux 发行版
    • 包括 systemd 初始化系统、现代化的文件系统、LVM2/EVMS、软件磁盘阵列(软 RAID)、udev 支持、initcpio(附带 mkinitcpio)以及最新的 Linux 内核
  • 打造简单高效、个性化的个人工作和学习环境(我称之为:ArchOS)
    • 采用滚动升级模式,尽全力为使用者提供最新的稳定版软件。(Pacman、AUR)
    • 避免任何不必要的添加、修改和复杂化的增加
    • 偏重使用命令行或文本编辑器修改各种设置
  • 社区文档非常清晰(中文翻译覆盖率高),是非常好的学习资料

🎯 目标:Full Control and understanding my sysytem。

The Arch Way:Archlinux 遵循的原则

简洁、保持最新,且注重实用

追求简洁,避免任何不必要的添加、修改和复杂化的增加,采用滚动升级策略,尽全力使软件处于最新的稳定版本。

Arch Linux 注重实用性,且尽力避免意识形态之争。最终的设计决策都是由开发者之间的共识决定,开发者依赖基于客观事实的技术分析和讨论,避免国家之间和政治因素的影响,不被世俗流行观点左右。

Arch Linux 的仓库中包含大量的软件包和编译脚本。用户可以按照需要进行自由选择。仓库中既提供了开源、自由的软件,也提供了闭源软件。

实用性大于意识形态。

以用户为中心

许多 Linux 发行版都试图变得更加“用户友好”,Arch Linux 则一直是,且永远会是“以用户为中心”。本发行版是为了满足贡献者的需求,而不是为了吸引尽可能多的用户。

Arch Linux 适用于乐于自己动手的用户,因为他们往往更愿意花时间阅读文档,解决自己的问题。

Arch Linux 鼓励每一个用户参与和贡献,报告和帮助修复 bugs,提供软件包补丁和参加核心项目:Arch 开发者都是志愿者,通过持续的贡献成为团队中的一员。Archers 可以自行贡献软件包到 Arch 用户仓库,提升 ArchWiki 文档质量,在 论坛邮件列表 或者 IRC 中给其它用户提供技术支持。

内容导读

第一部分,安装 Archlinux 及完成基础配置:

  1. 准备好引导安装的 U 盘
  2. 个性化安装 Arch Linux
  3. 基本的系统配置和应用

第二部分,打造个性化的 ArchOS:

根据实际需求阅读(可选):

资料来源

准备好引导安装的U盘

在使用 Arch Linux 打造个性化的工作机器前,我们先使用镜像文件制作启动 U 盘,用作救援系统、linux 安装程序或其他系统的基础。

1)下载镜像文件

简单的第一步,下载镜像文件:

如果担心下载的文件有安全隐患(比如从某网盘或 http 链接上下载), 可以验证签名:

gpg --keyserver-options auto-key-retrieve --verify archlinux-version-x86_64.iso.sig

对应 .sig 签名文件可以在官方镜像网站下载,这一步需要使用到 GnuPG 命令行工具软件

pacman-key -v archlinux-version-x86_64.iso.sig

签名验证软件: Gpg4win one Window | GPG Suite on MacOS

亦可以之后在 Arch Linux 中使用 Archiso 构建定制化的 ISO 映像,看个人需求。


2)制作系统启动 U 盘

无论是 Window 还是 MacOS 都有很多应用来把镜像文件写入 U 盘:

如果可以使用命令行工具,亦非常简单:

# List block,展示所有块设备:硬盘,闪存盘,CD-ROM等等,不会列出 RAM 盘的信息;
lsblk 
# 写入镜像文件到指定 u 盘
dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx status=progress && sync

# 在 macos 也有类似命令
diskutil list
sudo dd if=path/to/arch.iso of=/dev/rdiskX bs=1m


3)多系统引导 & 救援系统

可以多镜像系统引导的 U 盘

当你有多个操作系统、不同版本的镜像文件, 也不想一遍又一遍的格式化 u 盘,写入新镜像。

开源工具 Ventoy 是一个不错的选择: https://www.ventoy.net/cn/index.html, 只需要像正常的文件操作,把镜像文件扔到 u 盘就可以,同时 u 盘剩余空间还可以正常来使用存储文件。

救援系统

当系统出现不可预料的异常无法进入时,该 u 盘可以作为救援系统来使用,重装系统是很经常的用途,亦可以应付紧急状况的使用。

使用 chroot 是常见的用法, 改变根目录通常是为了在无法启动或登录的系统上进行系统维护:

推荐阅读:如何通过 chroot 恢复 Arch Linux 系统

个性化安装 Arch Linux

从 u 盘引导启动电脑进入 Live 环境后,先搞定网络问题,避免不少痛苦:

Reflector 是一个 Python 脚本(已默认安装),获取最新的镜像列表,筛选出最新的镜像并按速度排序,最后将结果写入到 /etc/pacman.d/mirrorlist 文件。

# 从中国地区的镜像中筛选出 5 个最新且支持 HTTPS 的镜像;
# 然后将结果覆写到 /etc/pacman.d/mirrorlist 文件内;
reflector --country china --protocol https --latest 5 --save /etc/pacman.d/mirrorlist

若安装过程无可用网络,可以使用本地镜像仓库的方式离线安装

安装脚本

在命令行键入 archinstall 即可自动化安装新系统:

Image.png

简单配置后,确认安装,等几分钟就可以安装成功,重启进入 Arch Linux。

手动安装:满足自定义需求及更多乐趣

磁盘分区

如同装修新居一样, 我们首先要规划设计一下系统盘的硬盘空间,无论是将系统安装 SATA 硬盘、 U 盘, 还是高速大容量的 SSD 硬盘。

分区方案有一些通用建议,但没有严格准则,实际的分区方案完全可以根据实际需求来定制:

Image.png

示例需求:将 Arch Linux 安装在一个 2TB 的固态移动硬盘中, 来规划分区方案:

Device挂载点Size 空间 | 类型 Type | 文件格式
/dev/sda1/mnt/boot512M | EFI System | FAT32
引导分区, 至少 300M;如果打算安装多个内核, 或者双系统,最好预留 1GB 以上空间
/dev/sda2[SWAP]32G |inux Swap|swap
Linux 交换空间,把硬盘空间作为虚拟内存,通常为交换分区分配两倍内存大小的空间,考虑是否会**使用休眠(挂起到硬盘)**的功能
/dev/sda3/mnt/512G|Linux filesystem | btrfs
新的文件系统 Btrfs,支持写时复制(CoW)、快照以及压缩等高级特性。子卷划分:
- / 根分区:主文件系统挂载, 必需。
- /home 独立分区:用户的配置和应用文件。
- /.snapshots: 备份快照文件数据存储分区。
- /var/log: 系统日志文件,单独分区存储。
- /var/cache: 下载的软件包缓存。
/dev/sda4/mnt/data剩余空间 |Linux filesystem |ext4
存放数据,跨操作系统的文件共享

这里使用 fdisk 工具来执行分区, 也有其他工具可以完成该操作, 比如 parted、cfdisk、gdisk:

# 对挂载硬盘(/dev/sda)进行分区操作,详细操作(m for help)
fdisk /dev/sdb
# 完成后, 格式化分区
mkfs.fat -F 32 /dev/sda1    # 格式化 boot 分区
mkswap /dev/sda2            # 格式化 swap 分区
mkfs.btrfs /dev/sda3        # 格式化主分区(根分区)
mkfs.ext4 /dev/sda4         # 格式化数据分区

对 btrfs 文件格式的主分区进行子卷划分时略微复杂, 需要先挂载根分区,才能创建子卷:

# 先挂载根分区
mount /dev/sda3 /mnt
# 根据实际需求创建子卷
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@cache
btrfs subvolume create /mnt/@snapshots
umount /mnt

# 重新挂载所有分区
# noatime 选项可以降低数据读取和写入的访问时间;
# compress 选项可以在数据写入前进行压缩,减少磁盘的写入量,增加磁盘寿命
# 在某些场景下还能优化一些性能,支持的压缩算法有 zlib、lzo 和 zstd,zstd 算法是最快的。
mount -o compress=zstd:1,noatime,subvol=@ /dev/sda3 /mnt
mkdir -p /mnt/{boot/efi,home,.snapshots,var/{cache,log}}
mount -o compress=zstd:1,noatime,subvol=@cache /dev/sda3 /mnt/var/cache
mount -o compress=zstd:1,noatime,subvol=@home /dev/sda3 /mnt/home
mount -o compress=zstd:1,noatime,subvol=@log /dev/sda3 /mnt/var/log
mount -o compress=zstd:1,noatime,subvol=@snapshots /dev/sda3 /mnt/.snapshots
mount /dev/sda1 /mnt/boot/efi
mount /dev/sda4 /mnt/data/
swapon /dev/sda2

# 这些目录将来并不会被保存快照,禁用写时复制:
chattr +C /mnt/var/log /mnt/var/cache /mnt/.snapshots

使用 lsblk /dev/sda 命令查看完成后的分区信息:

Image.png

在分区完成之后,安装一些必要的基础软件包后,再进行后续操作:

# 安装基础软件包 base、linux 内核及常规硬件的硬件到目标系统
# 选择计算机的微码包, 提升 CPU 稳定性:intel-ucode or amd-ucode
# 两个常用软件,方便后续操作:git、vim(文本编辑器,看个人喜好)
# (可选) btrfs-progs 包含了很多用于管理 btrfs 文件系统的命令
# 如果启动后只有无线网络可以使用,可以在这里提前安装 iwd 来验证登录 
# pacstrap 会拷贝你的镜像站配置文件(/etc/pacman.d/mirrorlist)到新系统
pacstrap -K /mnt base linux linux-firmware intel-ucode git vim btrfs-progs

# 将上一步文件目录挂载信息写入目标系统,未来也可以在此编辑挂载选项
# 用 -U 或 -L 选项设置 UUID 或卷标
genfstab -U /mnt >> /mnt/etc/fstab

# chroot 到新操作系统
arch-chroot /mnt
# 如果使用 btrfs 文件系统,需要修改 /etc/mkinitcpio.conf, 重新创建 Initramfs
  MODULES=(btrfs)
  BINARIES=(/usr/bin/btrfs)
# 根据预设文件生成镜像,无修改无需重复此步骤
mkinitcpio -P

# 设置 root 账户密码
passwd

我这里的系统是安装在可移动硬盘的,多做一步操作,避免 USB 省电模式导致卡死:

sudo echo -1 > /sys/module/usbcore/parameters/autosuspend

构建系统引导程序

小提示:这部分的操作都需要 chroot 到新系统内操作;

引导加载程序(Boot Loader,又称引导加载器、启动加载器或启动引导器)是由计算机固件(BIOSUEFI)启动的软件。

引导加载程序必须能够访问内核和 initramfs 映像,否则系统将无法引导。因此,在典型配置中,它必须支持访问 /boot 路径。

这是至关重要的一步,决定重启后是否可以正常进入系统;

方案一:systemd-boot

简单 UEFI 引导管理器,已包含在 Arch 的 systemd 软件包, 无需安装就可以使用:

# /boot/efi 为 EFI 系统分区实际挂载点,可以根据实际情况修改
bootctl install --path=/boot/efi

# 配置启动菜单,详细:https://wiki.archlinuxcn.org/wiki/Systemd-boot
vim /boot/loader/loader.conf 
  # 示例配置内容
  default  arch.conf
  timeout  4
  console-mode max
  editor   no
# 增加启动项, 熟悉之后可以根据实际需求定制
# 这里根分区的文件系统是 btrfs,所以增加了对应的参数
vim /boot/loader/entries/arch.conf
  # 启动项配置内容配置
  title   Arch Linux
  linux   /vmlinuz-linux
  initrd  /initramfs-linux.img
  options root=PARTUUID={YOUR ROOT PARTATION PARTUUID} zswap.enabled=0 rootflags=subvol=@ rw rootfstype=btrfs

# 如何查找某个分区的 PARTUUID, 比如根分区 /dev/sda3
blkid -s PARTUUID -o value /dev/sda3

方案二: GRUB 也是一种常见的选择

使用 pacman 安装需要的软件包:

pacman -S grub efibootmgr

安装 Grub 引导程序:

# EFI 系统分区目录:/boot/efi, 根据实际需求修改
# 如果是在 Mac 设备上安装,务必加上 --removable 选项
# --recheck 再次检查 device map,即使 /boot/grub/device.map 已经存在, 每当你添加/删除计算机中的磁盘时都应使用这一选项
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ArchOS

# 生成 Grub 配置文件
grub-mkconfig -o /boot/grub/grub.cfg

🎉 恭喜,到这里,你拥有了全新、基础的 Linux 操作系统。

exit & umount -R /mnt 
reboot # 重启进入新系统,用 root 账号登录 ⚡️

基础的系统配置和应用

重启新安装好 Arch Linux 的电脑,用 root 账号登录进入终端环境:

配置有线和无线网络

# 增加有线网络配置 vim /etc/systemd/network/0-wired.network
# 0-wired 的命名可以自己指定,数字开头方便排序
# 基本配置示例,DHCP=yes 来同时接收 IPv4 和 IPv6 DHCP 请求
  [Match]
  Name=en*
  [Network]
  DHCP=yes
# 启用 systemd-networkd、systemd-resolved 服务
systemctl enable --now systemd-networkd.service
systemctl enable --now systemd-resolved.service

# 如果需要连接无线网络,可以使用 iwd(iNet wireless daemon,iNet 无线守护程序)
pacman -S iwd & systemctl enable --now iwd.service
# 搜索并连接验证无线网络,手册:https://wiki.archlinuxcn.org/wiki/Iwd#iwctl
iwctl

# 强制更新所有软件
pacman -Syyu

拓展阅读: Linux ip 命令详解:网络配置工具

创建管理员账号

# 增加新用户账号 lone,并附加管理组 wheel, -m 选项创建用户主目录
useradd -m -G wheel lone
passwd lone
# 获取 root 执行权限,直接 su 切换到 root, 或者安装 sudo 
pacman -S sudo
export EDITOR=vim && visudo # 取消 %wheel  ALL=(ALL) ALL 行的注释
# 用创建后的用户身份登录之后,sudo 执行命令示例
sudo pacman -Sy

# ⚠️ 危险操作:禁止 root 账户登录
passwd -l root     # 解锁: sudo passwd -u root

区域和本地化的配置

# 🕰️ 同步更新系统时间, 选择合适的时区信息 Asia/Shanghai
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
hwclock --systohc
# 本地字符集, 支持英文和简体中文
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" >> /etc/locale.conf

# 设置 hostname 和 host 文件,记得更换 $hostname
echo "$hostname" >> /etc/hostname
echo "127.0.0.1 localhost" >> /etc/hosts
echo "::1       localhost" >> /etc/hosts
echo "127.0.1.1 $hostname.localdomain $hostname" >> /etc/hosts

基本的【中文】支持配置

# 设置环境变量 vim /etc/environment
# tty 终端支持中文显示是件复杂、无太大意义的事情,涉及修改内核,不建议折腾
export LANG=zh_CN.UTF-8
export LANGUAGE=zh_CN:en_US

# 安装中文字体:Android 设备的字体显示方案,你可以安装自己喜欢的字体
sudo pacman -S ttf-roboto noto-fonts noto-fonts-cjk adobe-source-han-sans-cn-fonts adobe-source-han-serif-cn-fonts ttf-dejavu
# 配置示例 https://wiki.archlinuxcn.org/wiki/字体配置/中文
vim ~/.config/fontconfig/fonts.conf 
fc-cache -fv  # 更新字体缓存

# 中文输入法的安装在后续桌面环境配置中说明;

科学上网能力配置

我们生活在现实世界中,有很多超越技术本身的缺陷, 但是解决掉网络限制问题确实会让后面的很多技术步骤少很多问题。 在这里只记录如何用 clash(已于 2023.11 停止更新) 来处理网络代理问题。

# 从官方仓库下载 clash 核心软件
pacman -S clash
# 引入配置文件,配置目录不存在可以直接创建
# 配置文件示例: https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml
# 可用的海外代理服务器需要你自己搞定。
vim ~/.config/clash/config.yaml 
# 让 clash 跟随系统自动启动, lone 代表当前用户,方便 clash 找到对应用户配置文件信息
systemctl enable --now clash@lone

# 设置环境变量,vim /ect/environment
export http_proxy=127.0.0.1:7890
export https_proxy=127.0.0.1:7890
export socks_proxy=127.0.0.1:7891

# 此时就可以 ping google.com 或者 github.com 看是否访问无碍
# 如果访问国外中遇到无法访问的域名或 ip 地址,尝试将其加入配置文件的代理规则,重启 clash 服务
systemctl status/restart clash@lone

你也可以尝试自己寻找办法解决这个限制,但是请仅在技术范畴,勿跨越雷池。

软件依赖管理

Pacman 是 Arch 的软件包管理器, 在安装过程中已经多次使用, 了解更多使用方式和技巧。

除了官方维护的软件仓库, 社区维护的用户仓库 AUR(Arch User Repository), 甚至可以为自己或团队自建本地仓库

安装 AUR 辅助工具: yay or paru

pacman -S --needed git base-devel
git clone https://aur.archlinux.org/yay.git && cd yay
makepkg -si  # 本地编译构建软件包

增加 ArchlinuxCN 社区维护的软件仓库,包含许多中文用户常用软件包:

# 编辑 /etc/pacman.conf,增加仓库地址配置
[archlinuxcn]
Server = https://mirrors.bfsu.edu.cn/archlinuxcn/$arch

# 刷新密钥
sudo pacman -Sy archlinuxcn-keyring

系统备份快照(btrfs 文件系统)与挂起到硬盘(即休眠)

使用 LinuxMint 团队开发的 Timeshift 来管理快照是个不错的选择, 使用上面装好的 AUR 工具:

# 安装必要软件包
pacman -S grub-btrfs && yay -S timeshift 

# 手动创建快照
timeshift --create --comments "Fall in archos."
# 重新生成 grub 配置文件时添加快照的入口, 方便故障排查
grub-mkconfig -o /boot/grub/grub.cfg

启用休眠到硬盘,首先配置 initramfs,编辑 /etc/mkinitcpio.conf, 在 HOOKS 中加入 resume

# resume 要在 udev 之后
HOOKS=(base udev autodetect modconf keyboard keymap consolefont block filesystems resume fsck)
# 重新生成 initramfs
mkinitcpio -P
# 添加内核参数 resume,指定 swap 设备,比如我们分区方案中的是 /dev/sda2
blkid -s UUID -o value /dev/sda2   # 找到设备的 UUID
# 编辑 /etc/default/grub 启动配置
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet resume=UUID={YOUR DEVICE UUID}"
# 顺手也自带的官方主题美化一下 Grub 启动界面
GRUB_THEME="/usr/share/grub/themes/starfield/theme.txt"

# 重新生成 grub 配置文件
grub-mkconfig -o /boot/grub/grub.cfg
# 重启之后,如果需要进入休眠状态
systemctl hibernate

日常维护使用需要了解的内容

现在我们拥有了一个开机可用的 Arch Linux,可以手动创建快照,方便出问题随时回滚。

跟随阅读后续的内容完成桌面环境、常见应用和开发环境的配置工作。

桌面环境及设备驱动

https://wiki.archlinux.org/title/General_recommendations

首先简单了解下 Linux 中 图形用户界面(GUI) 的基本架构:

  • 显卡驱动(GPU)与硬件输入
  • Display Server 显示服务器(X11、Wayland、Mir)
    • 桌面环境的基础,处理底层的绘图功能,。其他图形程序不直接在屏幕上绘图;相反,它们向显示服务器发送绘图请求,显示服务器为它们在屏幕上绘图
      • 简而言之,为应用程序提供像素访问
      • 需要像素访问的应用程序的另一个示例是屏幕录像机,它们通过显示服务器提供的 API 获取屏幕数据。
    • 负责管理客户端和硬件设备之间的通信
    • 负责绘制鼠标指针并控制其位置
  • Window Manager 窗口管理器:管理打开的窗口
    • 控制窗口的大小及其位置(根据您或应用程序的要求)
    • 窗口周围绘制装饰(标题栏和边框称为窗口装饰)
    • 窗口管理器通过与显示服务器通信来完成其工作。
    • 另一个重要功能是 窗口合成 :使得应用程序能够执行一些很酷的操作,例如允许透明度、模糊、绘制窗口阴影、在移动/最小化/最大化时为窗口设置动画以及其他视觉效果
  • Display Manager 显示管理器:控制用户会话并管理用户认证。
    • gdm / ssdm / lightdm
  • Desktop Environment 桌面环境:包括一套集成的应用程序和实用程序。
    • budgie / Cinnamon / Cutefish / Deepin
    • GNOME / KDE Plasma / LXDE / LXQt / MATE
    • Xfce

桌面环境代表了安装完整图形环境的最简单方法。然而,如果主流桌面环境都不能满足用户的要求,那么用户也可以自由地构建和定制自己的图形环境。一般来说,构建一个自定义环境需要选择一个合适的窗口管理器或混成器,一个任务栏和一些应用程序(一个最小的方案通常包括终端模拟器,文件管理器和文本编辑器)。

通常由桌面环境提供的其它应用程序有:

  • 屏幕锁定器
  • Polkit 身份认证组件
  • 登出对话框

一些流行的显示管理器有:

GDM ( GNOME 显示管理器(GNOME Display Manager) ):GNOME 的首选。

SDDM( 简单桌面显示管理器(Simple Desktop Display Manager) ):KDE 首选。

LightDM:由 Ubuntu 为 Unity 桌面开发。

X11很大程度上充当了客户端和窗口管理器之间的“一个非常糟糕的”通信协议

https://ifmet.cn/posts/cab49185/

The following desktop environments were tested with success

awesome bspwm budgie cinnamon deepin dwm enlightenment gnome i3 kde labwc lxde lxqt mate maxx pantheon qtile spectrwm sway windowmaker xfce xmonad Ly should work with any X desktop environment, and provides basic wayland support (sway works very well, for example).

屏幕定制

  • 捕获屏幕
  • 色温
  • 桌面壁纸配置器

任务栏

  • 通知守护程序
  • 音频控制
  • 背光控制
  • 媒体控制
  • 网络管理器
  • Power management

必要的程序

  • 应用程序加载器
  • 默认应用程序
  • 文件管理器

参考资料

  • https://linux.cn/article-12068-1.html
  • https://linux.cn/article-12773-1.html

复制粘贴的割裂问题必须搞定

开发环境准备

https://www.youtube.com/watch?v=dFkGNe4oaKk

NixOS: https://nixos.org/

常用软件及 Android 手机协同

游戏

基础概念索引

基础概念学习:

UEFI - Arch Linux 中文维基

Swap - Arch Linux 中文维基

分区 - Arch Linux 中文维基

微码 - Arch Linux 中文维基

udev - Arch Linux 中文维基

  • 快速识别命令中所使用的参数:https://explainshell.com/
  • https://endeavouros.com/
  • https://linux.cn/
  • https://crontab.guru/
  • https://www.phoronix.com/
  • 全面了解 Linux 游戏和 Steam Deck 的一切:https://gamingonlinux.com/
  • https://www.reddit.com/r/linux

https://learn.microsoft.com/en-us/linux/

参考资料

现代化的 Archlinux 安装,Btrfs、快照、休眠以及更多。 - 少数派

在虚拟机上面快速尝试熟悉一下:

UTM:开源的多面手 macOS 虚拟机(更新到 2023.10.30)

额外的情况

在 U 盘上安装: https://wiki.archlinuxcn.org/wiki/%E5%9C%A8%E5%8F%AF%E7%A7%BB%E5%8A%A8%E8%AE%BE%E5%A4%87%E4%B8%8A%E5%AE%89%E8%A3%85_Arch_Linux

离线安装: https://wiki.archlinuxcn.org/wiki/%E7%A6%BB%E7%BA%BF%E5%AE%89%E8%A3%85

ssh 远程安装:https://wiki.archlinuxcn.org/wiki/%E9%80%9A%E8%BF%87_SSH_%E5%AE%89%E8%A3%85_Arch_Linux

从现有 Linux 发行版安装 Arch Linux - Arch Linux 中文维基

性能优化 - Arch Linux 中文维基

在可移动设备上安装 Arch Linux - Arch Linux 中文维基

linux 挂载usb 休眠状态 关闭-掘金

https://github.com/microsoft/inshellisense

https://medium.com/@magda7817/sharded-does-not-imply-distributed-572fdafc4040

https://www.lazyvim.org/installation

https://store.kde.org/p/1350981

选择一把合适的剑 🗡️:Rust

选择主力的编程语言需要慎重,语言不仅仅是工具,还是一种思维方式。

Rust 是一门赋予每个人构建可靠且高效软件能力的语言,最初目的是用来编写以往都是由 C/C++ 编写的高性能程序(这类程序非常容易出现内存安全问题), 现在你已经可以在很多领域看到它的身影, 从初创公司到大型企业,从嵌入式设备到可扩展的 Web 服务,Rust 都完全适用。

语言特性

  • Performance 高性能
    • Minimal Runtime
    • No Garbage Collector 垃圾回收 ♻️
    • Zero-Cost-Abstractions
  • 安全可靠
    • Immutability & Privacy By Default
    • Ownership model guarantee memory-safety
    • thread-safety
    • No Null Values: ::Option (Some(T) or None)::
  • Type System, Enum, Match
  • Modern Tooling
  • 函数式编程:Closures、Iterators & Combinators
  • Async code and macro

用我的 Expound Note 或 figma 来输出图例;

  • 内存安全的重要性
  • Rust 使用所有权和借用模型来保证内存安全(Memory safety),同时也通过 unsafe 代码块来提供完全的灵活性,还没有 GC(Garbage Collector 垃圾回收机制)带来的性能损耗。
  • 为了让编译器(Compiler)深层次的理解你的代码,强类型系统(Rich Type System) 的支持就必不可少,编译器像你的老师或驾校教练,将更多的软件缺陷拦截在编译阶段。
    • 这是 Rust 的魔法 🪄:Code doesen‘t compile., 不要让它成为你学习之路的拦路虎。
    • 在编译器的指导下,可以写更好、不会轻易 Crash(错误和边界处理) 的软件。
    • 有点错误驱动开发的意思,让你自己也深刻理解你所写下的代码,像天才一样。
  • Rust 从函数式语言的重要特征学习了不少东西:基于 表达式(expression-based) 、函数闭包(Closures)与迭代器(Iterators), 以及强大的枚举(无处不在的 Option 和 Result),可以让你即拥有高级语言和功能,又不丢失低级语言的性能,而且这些都是零成本抽象的(Zero-Cost Abstract):无需为这些抽象付出成本。
  • 强大的 Macro System(宏系统)也带来一个好处:语言版本迭代带来的学习和迁移痛苦。
  • Rust 提供了完整的生态工具链和社区:
    • Cargo 依赖管理 📦
    • cargo fmt: 代码格式化 | cargo clippy: 代码检查 🧐
    • cargo test: 单元测试、文档测试 | cargo bench: 基础测试
    • rustup: Rust 安装、环境、版本切换等
  • 高级语言与低级语言
    • 元编程、模板语言、处理器 <<< Macros 图灵完全
      • 用 3D 打印机打印一个更好的打印机
      • 重写覆盖、DSL 语言
      • 在编译阶段运行、可以修改我们的源代码,即零成本抽象 ==> No Rust 2.0 & Long Compiler times
    • Direct Hardware Access <<< Unsafe Rust: if you know what are you doing & 内存安全,提供的灵活性,不需要学习新的语言

🦀️ Rust 代表一种全新的编程之路

掌握 Rust 不代表你的智商比他人高,但可以作为合格程序员的证书。

具备高性能、高可靠、高生产力的能力,拥有高级语言和低级语言的优势,适用于绝大多数行业领域,无畏并发,良好的语言生命力和社区生态,Rust 必将成为未来几十年的主流编程语言,值的长期投入。

写出更好的代码

好的代码要考虑诸多边缘条件以及不确定性,把这些知识都装进大脑或者期待人人训练有素是行不通的,Rust 提供的诸多功能来辅助你写出健壮、不会轻易奔溃的软件。

  • RELIABLE from the start
    • NO UNUSED VARIABLES, VALUE AND POINTER MUST BE VALID
    • 所有权和借用,你不会担心出现内存问题,并可以实现简单并发性
    • EXHAUSTIVE PATTERN MATCHING
    • ERRORS MUST BE HANDLED
  • Your Code WON'T Crash 在运行中,Rust makes you feel like a genius
  • PRODUCTIVE - Type System with Superpower
  • 缓解内卷(不只当螺丝钉)

Rust 之难,不在于语言特性,而在于:

  • 深层次理解核心理念,在实践中融会贯通的运用
  • 遇到了坑时(生命周期、借用错误,自引用等)如何迅速、正确的解决
  • 大量的标准库方法记忆及熟练使用,这些是保证开发效率的关键
  • 心智负担较重,特别是初中级阶段

不能抱着一直试一试的态度,Rust 的情况不允许你边工作,一边轻松愉快的学习,需要有计划的深入学习,系统性的提升编程能力,rust 无法看看语法就开始写代码,需要多学几次,深入进去,在克服诸多困难之后,会收获与众不同的编程之旅。

Roadmap 学习之旅

0)回顾一些计算机基础知识:

  • 计算机内存管理、垃圾回收
  • 指针、编译器原理
  • 操作系统和网络编程

1)编程语言的基础概念

  • 变量、基本类型以及复合类型
  • 重要的集合类型
  • Function and Control Flow、
  • Structs、泛型、Traits

2)Rust 语言的难点

为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及 Rust 最核心的原则——所有权

  • 所有权、借用和引用
  • 生命周期、全局变量
  • 循环引用和自引用问题
  • 指针、切片类型

3)工程能力

  • The module system
  • 错误处理
  • 代码组织:Cargo、Package、Crates、注释及文档
  • 自动化测试 https://www.youtube.com/watch?v=8XaVlL3lObQ

Building & Package Management

https://www.youtube.com/watch?v=dFkGNe4oaKk

4)进阶学习

  • 函数式编程:闭包、迭代器
  • 智能指针
  • 多线程并发编程
  • async/await 异步编程
  • Unsafe Rust
  • Marco 宏编程, Extensibility https://www.youtube.com/watch?v=MWRPYBoCEaY
    • DSL & 元编程
    • They run at compile time
    • They can modify your sources code
    • MARCOS are a build tool inside your code

项目实战

编程语言基础

我们需要先学习一些 Rust 的基础知识,这也是学习任何编程语言的初级知识,如果你还没接触过任何编程语言,从 Rust 作为起点是不错的选择,一通百通。

安装 🛠️

  1. 跟随官网的入门教程rustup.sh 的指引完成 Rust 环境安装。

  2. 学会使用 Cargo 来新建、构建、发布项目。

目标 🎯

1)搞定常见的编程概念

就像学习英文需要首先学习字母、发音、语法等基础,也需要先搞懂编程语言中常见的概念。

2)常见的集合 Collections

集合就像装数据的容器,经常会用到。

3)用代码构建想法

构建想法(Design Code) Structs | 枚举 Enum | Rich Types

入门教程及书籍导读 📚

《Rust 程序语言设计》:过一遍可以让对 Rust 有了解:

适合快速复习巩固基础的教程:

不喜欢文档学习和书籍阅读,实践向课程:

不错的书籍,有时间可以看看(如果不买正版书又想省钱,可以去多抓鱼等二手书上购买):

重学好几遍没入门的同学,系统性深入学习和推荐(付费课程非广告,自行斟酌):

Rust 入门教程(仅记录,非推荐):

  • Easy Rust:dhghomon.github.io/easy_rust/
  • Possible Rust:https://www.possiblerust.com/about/
  • https://stevedonovan.github.io/rust-gentle-intro/
  • https://learning-rust.github.io/docs/why-rust/
  • https://www.freecodecamp.org/news/rust-in-replit/
  • https://www.programiz.com/rust
  • https://www.educative.io/courses/learn-rust-from-scratch
  • https://learn.microsoft.com/en-us/training/paths/rust-first-steps/
  • https://www.oreilly.com/library/view/rust-programming-by/9781788390637/
  • https://www.youtube.com/@RustVideos

📜 官方文档:

任何时候,如果你拿不准标准库中的类型或函数的用途和用法,不要慌或只会 Google,请查阅应用程序接口(application programming interface,API)文档和源码可能更高效!

  • 常用的Rust标准库: std - Rust
  • Reference:https://doc.rust-lang.org/reference/index.html
  • 第三方库文档中心:docs.rs

常见的编程概念

变量绑定、解构与可变性

变量绑定(variable binding),为什么不叫变量赋值后面会聊到:

#![allow(unused)]
fn main() {
// &str 明确指定变量的类型,也可省略让编译器自行推导
let name: &str = "Rust"; 
// 也可以先声明(declare),再初始化值(initialize)
let name: &str; // 没有初始化之前是不允许使用
name = "Rust";
// 重新绑定会覆盖(Shadowing)之前的值
let name = "Rust Lang";
// 解构式绑定,先简单了解
let (name, age) = ("Anonymous", 23);
println!("Name: {}, Age: {}", name, age);
}

常见操作是去改变一个变量的值:

#![allow(unused)]
fn main() {
let name = "Rust";
name = "Rust Lang"; // Err 会编译出错
}

在 Rust 中变量默认是 🙅 不可变(immutable) 的,这可以带来两个好处:

  • 语言层级的安全性和简单并发性
  • 变量声明为不可变在运行期可以避免多余的 runtime 检查,提升性能

不用担心会因此损失灵活性,需要时手动增加 mut 声明即可:

#![allow(unused)]
fn main() {
let mut name = "Rust"; // mut: mutable 可变的
name = "Rust Lang"; // It's ok.
println!("Name is {}", name);
}

如需一个完全不允许改变的值,可以使用常量(constants):

// 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
// 常量可以作为多处代码使用的全局范围的值, 包括一些需要硬编码的值
const SPEED_OF_LIGHT = 299792458;

数据类型

Rust 是静态类型(statically typed)语言, 在编译时就必须知道所有变量的类型,编译器通常可以推断出我们想要用的类型。

标量(Scalar):代表一个单独的值

标量说明备注
整型有符号和无符号整数Decimal (十进制): 98_222
Hex (十六进制): 0xff
Octal (八进制) : 0o77
Binary (二进制): 0b1111_0000
Byte (单字节字符)(仅限于u8) : b'A'
浮点型f32 和 f64避免在浮点数上测试相等性
布尔类型true 和 falselet f: bool = false; // with explicit type annotation
字符类型char字符只能用 '' 来表示, "" 是留给字符串

复合类型 Compound Types:多个值组合成一个类型

Tuple 元组类型:将多个不同类型的值组合进一个复合类型

  • 元组长度固定:一旦声明,其长度不会增大或缩小。
  • () 表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。
#![allow(unused)]
fn main() {
let language = "The Rust Language";
let tup: (&str, usize) = (language, language.len());
// . 操作符来获取元组中的值。
println!("The language is: {}", tup.0);
}

Array 数组类型:数组中的每个元素的类型必须相同

  • 数组在栈 (stack) 上分配的已知固定大小的单个内存块,长度固定
#![allow(unused)]
fn main() {
// 数组类型说明语法:[&str; 12]
let months: [&str; 12] = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];

// 使用索引来访问数组的元素, 从 0 开始计数,越界访问会导致编译报错,避免非法的内存访问
let dec = months[11];
println!("Now is {}", dec);

// 数组切片 slice,从数组中 Array 从切出子数组
let the_first_season: &[&str] = &months[0..=2]; // 也可以简化为:&months[..3]
println!("The first season: {:?}", the_first_season);
}

Enums 枚举

列举所有可能的成员(变体 Variant)来定义的数据类型,在 Rust 中非常有用且常见。

不少现实世界的事务(可穷举)都可以用枚举来表示(月份、星期、方向等)。

#![allow(unused)]
fn main() {
#[derive(Debug)]
enum Message {                  // 定义枚举类型来表示系统消息
    Quit,                       // 无数据的枚举成员
    Move { x: i32, y: i32 },    // 包含匿名结构体
    Write(String),              // 包含 String 字符串
    ChangeColor(i32, i32, i32), // 包含元组结构体
    Other,                      // 其他消息数据
}
let msg = Message::Write("Start Success!");

#[derive(Copy, Clone)]
#[repr(u8)]   // 限定范围为`0..=255`
enum Week {   // 定义英文的星期和数值相对应的枚举
  Monday = 1, // 1
  Tuesday,    // 2
  Wednesday,  // 3
  Thursday,   // 4
  Friday,     // 5
  Saturday,   // 6
  Sunday,     // 7
}

impl Week {   // 为枚举类型定义方法 Methods
  fn is_weekend(&self) -> bool {
    if (*self as u8) > 5 {
      return true;
    }
    false
  }
}

let mon = Week::Monday as i32;  // 使用 as 将 enum 成员转换为对应的数值。

// 用 Rust 实现 Json 解析工具,定义枚举类型去枚举 Json 允许的数据类型
// 在其他语言中,可能需要定义很多方法来表达出这些内容
enum Json {
  Null,
  Boolean(bool),
  Number(f64),
  String(String),
  Array(Vec<Json>),
  Object(Box<HashMap<String, Json>>),
}
}

常见的枚举 OptionSome or None 只是省略了 Option:: 前缀, 本质上是枚举值;

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),    // Some(T) 可以包含任何类型的数据
    None,       // 表示空值
}
}

对 Option 进行运算之前必须将其转换为 T(unwrap 方法)。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况,消除了错误地假设一个非空值的风险,会让你对代码更加有信心。

这是 Rust 的经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。

枚举类型和下面的模式匹配控制流是绝佳拍档。

函数与流程控制

Rust 是一门基于 表达式(expression-based) 的语言(函数式语言的重要特征),需要理解其区别。

程序是由一行行指令编写而成,把每一行代码区分为:

  • 语句 Statements:执行一些操作但不返回值的指令;
  • 表达式 Expressions:计算并产生一个值;
  • 代码注释 Comments
// main 函数是程序的入口点,等同于 main() -> (), 无返回值
fn main() {
    let res = add(1, 1); // 函数调用是表达式,会求值
    println!("1 + 1 = {}", res); 

    // 大括号创建的一个新的块作用域也是一个表达式
    let z = {
        let x = 3;  // 等同于 let x = { 3 };
        x + 1
    };
}
// fn 关键字用来声明新函数,函数名 add, 参数 x、y, 返回值类型 i32
fn add(x: i32, y: i32) -> i32 {
    x + y // 表达式,等同于 return x + y; 语句
          // 如果在此加上 ; 就成了一个语句,整个函数其实会返回 (),产生报错
}

根据条件来运行不同代码的是大部分编程语言的基本组成部分,常见的流程控制语法, 也都是表达式:

#![allow(unused)]
fn main() {
let result = if condition { // 这样赋值时需要保证每个赋值返回的值是同类型
    // A...
} else { // 或 else if
    // B...
}

// 循环执行:for / while / loop
// for 并不会使用索引去访问数组,安全且简洁,同时避免运行时的边界检查,性能更高
for i in 1..=5 { 
    if i == 2 {
        continue; // 跳过当次循环,break 可以跳出终止整个循环
    }
    println!("{}", i); 
}
for item in container { } // 循环中修改元素,使用 mut 关键字: &mut container
for (index, value) in array.iter().enumerate() // 获取数组中元素的索引

// 也可以用 loop + if 来组合实现
while n <= 5  {
    println!("{}!", n);
    n = n + 1;
}
}

模式匹配(Patern match)

match 控制流称得上是神兵利器,为复杂的类型系统提供了非常棒的解构能力。就像绝世锦囊,确保所有可能的情况都得到处理,算无遗策。

无论是多个 if-else 的条件分支判断场景,还是根据枚举类型的值匹配执行不同的任务,match 模式匹配都可以有更高效、简练的代码表达(可以少写不少代码)。

#![allow(unused)]
fn main() {
// Option 的本质是一个枚举值,有两个成员:Some<T> | None
let r: Option<&str> = Some("Rust");   
println!("{}", r.unwrap());

// unwrap 的实现就是利用了 macth, 等同于:
match r {                           // 对 Option 进行模式匹配
    Some(i) => println!("{i}"),     // 处理不同枚举值的执行,此处 Some 内部的值绑定了变量 i:&str
    None => println!("None")        // 必须处理所有情况,匹配是穷尽的
} // match 也是表达式,会返回值, 可以使用 macth 来赋值
}

再看一个示例,比如我们玩掷骰子 🎲 游戏,如果结果是小于等于 3, 会失去 1 积分,如果结果是 6 会获得 2 积分,其他情况积分不变:

#![allow(unused)]
fn main() {
match dice_roll {
    1 | 2 | 3 => lose(),    // 或者范围表达式:1..=3
    6 => win(),
    _ => do_nothing(),  // _ 占位符代表其他的值,满足穷举性
}
}

macthes! 宏与变量遮蔽的细节:

enum MyEnum { Foo, Bar }

fn main() {
    let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];
    v.iter().filter(|x| matches!(x, MyEnum::Foo));

    let foo = 'f';
    assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

    let bar = Some(4);
    assert!(matches!(bar, Some(x) if x > 2));

    // match or if-let 都是新的代码块,此处绑定相当于新变量
    let age = Some(30);
    match age {
        // 此处绑定相当于 let age = 30; 原先的 age: Some(30) 被覆盖了 
        Some(age) =>  println!("将 Some 内部数据({})绑定到变量 age", age),
        _ => ()
    }
}

蛮多情况,我们只想处理匹配某个模式的情况,而不关心其他匹配情况,可以简写程序:

#![allow(unused)]
fn main() {
// 只处理有配置数值的情况
let config_max = Some(3u8);
match config_max {
    Some(max) => println!("The maximum is configured to be {}", max),
    _ => (),
}

// 用 if-let 控制流简写
if let Some(max) = config_max {
    println!("The maximum is configured to be {}", max);
}
}

简单解释一下,let PATTERN = EXPERSION 变量绑定是一种最简单的模式匹配,将表达式与模式匹配,并对匹配成功高的进行变量绑定(赋值),回想一下变量绑定的知识:

#![allow(unused)]
fn main() {
let x = 1;
let (x, y) = (0, 1);    // 元组的解构赋值,x=0,y=1

// 现在是不是更容易上面理解 if-let 操作啦,同理,while-let 也没啥问题
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() { // let 模式匹配,变量绑定 top
  println!("{}", top);  // 没有元素 pop, 返回 None,模式无法匹配,退出循环
}

// for (idx, value) in ids.iter().enumerate() {} 
// for 迭代同样也是模式匹配的过程,给控制变量绑定赋值

fn add(x: i32, y:i32) {}// 函数参数传值,本质也是在做模式匹配(参数绑定)的操作
}

咋样,模式匹配非常有用吧,看透本质,学习神速 ⚡

模式匹配的高级用法

  • 解构后进行模式匹配时,如果某个值没有对应的变量名,则可以使用 @ 手动绑定一个变量名;
  • ref 和 mut 修饰模式匹配中的变量(所有权和可变性);
  • 匹配守卫(match guard),允许匹配分支添加额外的后置条件;
  • 解构赋值时,如果解构的是一个引用,则被匹配的变量也将被赋值为对应元素的引用;
  • 对解引用(deref)进行匹配时, 可能会发生所有权转移;(所有权的难点)

References

  • 关键字:https://kaisery.github.io/trpl-zh-cn/appendix-01-keywords.html
  • 命名规范:https://course.rs/practice/naming.html

常用的集合 Collections

除了基本数据类型或固定大小的数组或元组存储特定的值,Rust 标准库还提供不少非常有用的集合数据结构来存放动态数据

1)动态数组 Vector

Vec 存放多个相同类型的值:

#![allow(unused)]
fn main() {
// `Vec` is a regular struct,为了后续可以修改 vector, 上面加上了可变声明 mut
// 换种写法亦可:let v = vec![]; , 不要被宏 !写法吓到
// 如可以预估数组长度,可以在初始化时指定 Vec::with_capacity(5);
let mut v: Vec<i32> = Vec::new();	
v.push(0);			// 更新 vector
v.extend([1, 2]);		// 拼接 vector

for i in &mut v { *i += 1; } 	// 可以循环遍历, *i 解引用获取值

let len = v.len();		// 获取 vector 长度
let last: i32 = v.pop()		// 这里获取了可变引用,如果不理解,学习了所有权章节后再回顾
		.unwrap();		// 并对 pop 方法返回 Option 枚举直接 unwrap
let one: i32 = v[0];		// 读取 vector 中的元素, 等同于 v.get(0),不过要注意返回 Result
println!("Length of this vector is {len}. First is:{one}. Last is {last}");
}

巧妙利用枚举来存放不同数据类型的值:

#![allow(unused)]
fn main() {
#[derive(Debug)]
enum TableCell {	// 定义一个枚举类型表示电子表格的单元格数据
	Int(i32),		// 可能是数字
	Float(f64),		// 可能是浮点值
	Text(String),	// 可能是字符串
}

let row = vec![TableCell::Int(3), TableCell::Float(3.14), TableCell::Text("Rust".to_string())];

for (index, cell) in row.iter().enumerate() { 
	let pos = index + 1;
	println!("Cell {pos} is {:?} .", cell); 
}
}

初始化 vec 的更多方式:

fn main() {
    let v = vec![0; 3];   // 默认值为 0,初始长度为 3
    let v_from = Vec::from([0, 0, 0]);
    assert_eq!(v, v_from);
}

动态数据都在内存空间存放在堆(Heap)上,在内存中彼此相邻排列。

动态数组意味着我们增加元素时,如果容量不足就会导致 vector 扩容(目前的策略是重新申请一块 2 倍大小的内存,再将所有元素拷贝到新的内存位置,同时更新指针数据),显然,当频繁扩容或者当元素数量较多且需要扩容时,大量的内存拷贝会降低程序的性能。

可以考虑在初始化时就指定一个实际的预估容量,尽量减少可能的内存拷贝:

fn main() {
    let mut v = Vec::with_capacity(10);
    v.extend([1, 2, 3]);    // 附加数据到 v
    println!("Vector 长度是: {}, 容量是: {}", v.len(), v.capacity());

    v.reserve(100);        // 调整 v 的容量,至少要有 100 的容量
    println!("Vector(reserve) 长度是: {}, 容量是: {}", v.len(), v.capacity());

    v.shrink_to_fit();     // 释放剩余的容量,一般情况下,不会主动去释放容量
    println!("Vector(shrink_to_fit) 长度是: {}, 容量是: {}", v.len(), v.capacity());
}

Vector 常见的一些方法示例:

#![allow(unused)]
fn main() {
let mut v =  vec![1, 2];
assert!(!v.is_empty());         // 检查 v 是否为空

v.insert(2, 3);                 // 在指定索引插入数据,索引值不能大于 v 的长度, v: [1, 2, 3] 
assert_eq!(v.remove(1), 2);     // 移除指定位置的元素并返回, v: [1, 3]
assert_eq!(v.pop(), Some(3));   // 删除并返回 v 尾部的元素,v: [1]
assert_eq!(v.pop(), Some(1));   // v: []
assert_eq!(v.pop(), None);      // 记得 pop 方法返回的是 Option 枚举值
v.clear();                      // 清空 v, v: []

let mut v1 = [11, 22].to_vec(); // append 操作会导致 v1 清空数据,增加可变声明
v.append(&mut v1);              // 将 v1 中的所有元素附加到 v 中, v1: []
v.truncate(1);                  // 截断到指定长度,多余的元素被删除, v: [11]
v.retain(|x| *x > 10);          // 保留满足条件的元素,即删除不满足条件的元素

let mut v = vec![11, 22, 33, 44, 55];
// 删除指定范围的元素,同时获取被删除元素的迭代器, v: [11, 55], m: [22, 33, 44]
let mut m: Vec<_> = v.drain(1..=3).collect();    

let v2 = m.split_off(1);        // 指定索引处切分成两个 vec, m: [22], v2: [33, 44]
}

当然也可以像数组切片的方式获取 vec 的部分元素:

fn main() {
    let v = vec![11, 22, 33, 44, 55];
    let slice = &v[1..=3];
    assert_eq!(slice, &[22, 33, 44]);
}

更多技术细节,阅读 Vector 的标准库文档

数组排序是重要且常用的知识,了解一下:

#![allow(unused)]
fn main() {
let mut vec = vec![1, 5, 10, 2, 15];    
vec.sort();    
print!("{:?}", vec);

// 如果 vector 内的数据类型无法直接比较,也可使用 sort_by 和闭包(后面会学到)来实现
// 比如我们计划把存放个人数据的 vector 按照年龄倒序排列:
// peoples.sort_by(|a, b| b.age.cmp(&a.age))	// 示例代码,无法运行, age:u32
}

非稳定排序 sort_unstable 和 sort_unstable_by 的说明,所谓的非稳定并不是指排序算法本身不稳定,而是指在排序过程中对相等元素的处理方式。在稳定 排序算法里,对相等的元素,不会对其进行重新排序。而在非稳定的算法里则不保证这点。

总体而言,非稳定排序的算法的速度会优于稳定排序算法,同时,稳定排序还会额外分配原数组一半的空间。

延伸阅读

2)字符串 str

字符串在 Rust 中反而是一个难点,要多花点心思理解透彻,人类语言本身亦很复杂。

在语言级别,只有字符串切片类型 str (切片类型本质上是一个胖指针),&str 表示为 str 类型的引用。

Rust 标准库提供的字符串类型:String、OsString、OsStr、CsString、CsStr 等。

字符串字面量(比如 "THE RUST LANGUAGE.",双引号包围),被编译器做了特殊处理,硬编码写入程序二进制文件中,并不是直接存到内存的堆(Heap)或者栈(Stack),程序加载时,放在单独的地方(内存静态数据区的全局字面量区)。

str 类型无法被修改,String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串。

fn print_type_name<T>(_val: &T) {
    println!("{}", std::any::type_name::<T>());
}

fn main() {
	let lang = "THE RUST LANGUAGE."; // lang: &str,存放在栈上
	println!("{lang}");

	// "Let's Rusty!" 在程序加载期间被放入字面量内存区 Literals
	// String::from 从字面量内存区将其拷贝到内存堆(Heap)上, 
	// 然后将堆内存中该数据的地址保存到栈(Stack)内变量 hello 中
	let hello = String::from("Let's Rusty!"); // 等同 .to_string() 即 &str => String
	// &hello: String => &[String], &hello.as_str(): String => &str
	print_type_name(&hello);  // alloc::string::String
}

字符串 String 的底层的数据存储格式实际上是[u8]:一个字节数组,而不同语言的字符长度不一,通过索引的方式获取某个字符是不可预期的。

操作字符串:

#![allow(unused)]
fn main() {
let mut s = String::from("Hello ");

s.push_str("rust");			// 附加 &str	, 亦可以: s + "rust!";
s.push('!');	
s.insert(5, ',');				
s.insert_str(6, " i like");		// 插入到指定索引 &str
let mut s1 = s.replace("rust", "RUST");	// 替换 
s1.replace_range(7..8, "r");		// 替换指定范围,直接操作原字符串

println!("{s1}");

// 其他方法:remove、truncate、clear

// 遍历字符操作: 你好 👋
for c in "السلام عليكم".chars() { print!("{c} "); } // ا ل س ل ا م   ع ل ي ك م
for b in "Здравствуйте".bytes() { print!("{b} "); }
// 181 151 208 180 209 128 208 176 208 178 209 129 209 130 208 178 209 131 208 185 209 130 208 

}

关于字符串转义、Unicode 字符、UTF-8 的知识:

  • https://crates.io/crates/utf8_slice
  • https://doc.rust-lang.org/std/string/struct.String.html#utf-8

3)哈希 HashMap

实际业务中有很多键值(key-value)对应的数据,比如游戏排行榜:每个玩家和对应的分数,我们可以用 HashMap<k, v> 来存储, 它通过一个 哈希函数(hashing function) 来实现映射,决定如何将键和值放入内存中。

哈希 map 将它们的数据储存在堆上,所有的键必须是相同类型,值也必须都是相同类型。

#![allow(unused)]
fn main() {
// 不常用,没有被 prelude 自动引入:https://doc.rust-lang.org/std/prelude/index.html
use std::collections::HashMap;	

let mut scores = HashMap::new();
let a = "Player A";	// 键类型为字符串切片引用:&str 
let b = "Player B";	
scores.insert(a, 10); 
scores.insert(b, 50);

scores.insert(a, 20);	// 如果键已经存在,会覆盖
scores.entry(b).or_insert(60);	// 只在键没有对应一个值时插入, 所以这里不会更新数据

// 任意顺序打印出每一个键值对
for (key, value) in &scores { println!("{key}: {value}"); }
}

常见的代码示例:统计单词频次

#![allow(unused)]
fn main() {
let mut map = std::collections::HashMap::new();
for word in "hello rust hello world wonderful world, rust".split_whitespace() {
	// or_insert 方法返回这个键的值的一个可变引用(&mut V), 存放在 count 变量
	let count = map.entry(word).or_insert(0); 
	*count += 1;	// * 解引用,重新赋值,更新单词的统计数
}
println!("{:?}", map);
}

程序需要储存、访问和修改数据时,来回顾学习,更多集合数据类型,可以在后续需要时学习。

Design Code

这个章节的目的是教会你如何设计代码逻辑,如何思考,如何实现。

面向对象

Methods

Structs and Traits

impl for struct、struct / function can be generic

Generic Types 泛型 & Trait & Lifetime

Trait

impl Trait for Struct, default impl

Trait Object

Assoacited Trait

Think in expression | Iterators | Option | Rich Types

Zero Cost Abstraction(CPU 概念的举例,只存在我们脑海,本质是 01) - OPtion and Result 随处可见、零成本抽象、迭代

Polymorphism

  • No Classical Inheritance

  • Traits

  • Generics

  • Design Code

    • Build Structure, Convert Business Knowledge to Type System/Enum
      • No Classes
      • ALGEBRAIC TYPE SYSTEM
      • data normalization
      • state machines
  • Write Code / Read Code / Test Code

    • Work with compiler.
  • Maintenance Code / Support their Code

    • Trust code, 少维护

Understanding Ownership 所有权系统

::A way to manage memory,:: you need fighting with the borrow checker.

图书的抽象表达。

Stack and Heap

理解Rust内存管理 - Rust入门秘籍

20 张图揭开「内存管理」的迷雾,瞬间豁然开朗

Ownership Rules

  1. Each value in Rust has a variable that's called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

String and slice

基本概念介绍:https://www.youtube.com/watch?v=TJTDTyNdJdY&list=PLZaoyhMXgBzozt1LeHkCv8ERUPxhXT1eE

借用(borrowing)

**获取变量的引用,**常规引用是一个指针类型,指向了对象存储的内存地址。

*T 解引用,解出引用所指向的值。

同一作用域,特定数据只能有一个可变引用。

变量在离开作用域后,就自动释放其占用的内存。其实,在 C++ 中,也有这种概念: Resource Acquisition Is Initialization (RAII)。如果你使用过 RAII 模式的话应该对 Rust 的 drop 函数并不陌生。

v2-8cc4ed8cd06d60f974d06ca2199b8df5_1440w.png

把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段

https://www.youtube.com/watch?v=1QoT9fmPYr8

The Smart Pointer

https://www.youtube.com/watch?v=CTTiaOo4cbY&list=PLZaoyhMXgBzozt1LeHkCv8ERUPxhXT1eE&index=2

指针:a reference,指代包含内存地址的变量,这个地址被用于索引。

  • & 符号,引用,Rust 中常见的一种指针
    • 符号,解引用行为,跟踪引用并跳转到它指向的值。
  • 智能指针(smart pointer)则是一些数据结构,它们的行为类似于指针但拥有额外的元数据和附加功能
    • Struct + Deref + Drop
    • ::Deref trait:: 使得智能指针结构体的实例拥有与引用一致的行为
    • ::Drop trait:: 则使你可以自定义智能指针离开作用域时运行的代码
  • String 和 Vec 都可以被算作智能指针类型。
  • Rc (Reference Counting):引用计数智能指针类型。
    • Rc类型的实例会在内部维护一个用于记录值引用次数的计数器,从而确认这个值是否仍在使用
    • Rc的Drop实现会在Rc离开作用域时自动将引用计数减1。
    • Rc通过不可变引用使你可以在程序的不同部分之间共享只读数据。
    • 单线程场景使用
  • Box : 用于在堆上(Heap)分配值,栈中保留一个指向堆数据的指针。
    • 当你拥有一个无法在编译时确定大小的类型,但又想要在一个要求固定尺寸的上下文环境中使用这个类型的值时。
    • 当你需要传递大量数据的所有权,但又不希望产生大量数据的复制行为时
    • 当你希望拥有一个实现了指定trait的类型值,但又不关心具体的类型时。
  • Ref 和 RefMut 可以通过RefCell访问,是一种可以在运行时而不是编译时执行借用规则的类型。

引用是只借用数据的指针;而与之相反地,大多数智能指针本身就拥有(所有权)它们指向的数据。

解引用转换(Deref coercion)

Rust通过实现解引用转换功能,使程序员在调用函数或方法时无须多次显式地使用&和*运算符来进行引用和解引用操作。

• 当T: Deref<Target=U>时,允许&T转换为&U。

• 当T: DerefMut<Target=U>时,允许&mut T转换为&mut U。

• 当T: Deref<Target=U>时,允许&mut T转换为&U。

引用计数

调用Rc::clone来增加Rc实例的strong_count引用计数。

使用Rc的引用来调用Rc::downgrade函数会返回一个类型为Weak的智能指针,这一操作会让Rc中weak_count的计数增加1,而不会改变strong_count的状态。Rc并不会在执行清理操作前要求weak_count必须减为0。

强引用可以被我们用来共享一个Rc实例的所有权,而弱引用则不会表达所有权关系。一旦强引用计数减为0,任何由弱引用组成的循环就会被打破。因此,弱引用不会造成循环引用。

内部可变性(Interior Mutability)

允许你在只持有不可变引用的前提下对数据进行修改 unsafe。对于使用一般引用和Box的代码,Rust会在编译阶段强制代码遵守这些借用规则。而对于**::使用RefCell的代码,Rust则只会在运行时检查这些规则::**,并在出现违反借用规则的情况下触发panic来提前中止程序。

运行时的借用规则检查同样能够帮助我们避免数据竞争。

RefCell类型代表了其持有数据的唯一所有权

在创建不可变和可变引用时分别使用语法&与&mut。对于RefCell而言,我们需要使用borrow与borrow_mut方法来实现类似的功能。borrow方法和borrow_mut方法会分别返回Ref与RefMut这两种智能指针,由于这两种智能指针都实现了Deref,所以我们可以把它们当作一般的引用来对待。

RefCell会记录当前存在多少个活跃的Ref和RefMut智能指针。每次调用borrow方法时,RefCell会将活跃的不可变借用计数加1,并且在任何一个Ref的值离开作用域被释放时,不可变借用计数将减1。RefCell会基于这一技术来维护和编译器同样的借用检查规则:在任何一个给定的时间里,它只允许你拥有多个不可变借用或一个可变借用

::Rc + RefCell::

Rc允许多个所有者持有同一数据,但只能提供针对数据的不可变访问。如果我们在Rc内存储了RefCell,那么就可以定义出拥有多个所有者且能够进行修改的值了。

其他类型

Cell选择了通过复制来访问数据

Introduction - The Rustonomicon

❌ 错误处理

不好的代码编写策略:

  • try catch
  • GOTO
  • You Can‘t fix NULLS,NO NULLS

依赖于用文档去处理错误

Railway Oriented Programming: https://fsharpforfunandprofit.com/rop/

Write Code That Never Crashes

  • Options and Result
  • 使用 Enum 枚举自定义错误分支

关于 Rust 错误的信息,比你想知道的更多

引用 Rust Book 中的话:“错误是软件生活中的一个事实”。这篇文章讲更细致地讨论如何处理它们。很值得一读

一个非常简单的例子,访问数据数组的方式。itemCount[n],rust 需要你覆盖所有错误

进阶内容

Closures 闭包

闭包可以从环境中捕获值,与函数接受参数的方式是完全一致的:获取所有权、可变借用及不可变借用,编码表现为 3 种 Trait:

  • ::FnOnce:::意味着闭包可以从它的封闭作用域中,也就是闭包所处的环境中,消耗捕获的变量。为了实现这一功能,闭包必须在定义时取得这些变量的所有权并将它们移动至闭包中,因为闭包不能多次获取并消耗同一变量的所有权,所以只能被调用一次(Once)
  • ::FnMut:::可以从环境中可变地借用值并对它们进行修改。
  • ::Fn:::可以从环境中不可变地借用值。

当你创建闭包时,Rust会基于闭包从环境中使用值的方式来自动推导出它需要使用的trait。所有闭包都自动实现了FnOnce,因为它们至少都可以被调用一次。那些不需要移动被捕获变量的闭包还会实现FnMut,而那些不需要对被捕获变量进行可变访问的闭包则同时实现了Fn。

使用 ::move:: 关键字,强制闭包获取环境中值的所有权。

Iterator 迭代器

在Rust中,迭代器是惰性的(layzy)。这也就意味着创建迭代器后,除非你主动调用方法来消耗并使用迭代器,否则它们不会产生任何的实际效果。

iter() | iter_into() | iter_mut()

迭代器可以让开发者专注于高层的业务逻辑,而不必陷入编写循环、维护中间变量这些具体的细节中。

运行期初始化静态变量

使用lazy_static在每次访问静态变量时,会有轻微的性能损失,因为其内部实现用了一个底层的并发原语std::sync::Once,在每次访问该变量时,程序都会执行一次原子指令用于确认静态变量的初始化是否完成

https://github.com/rust-lang-nursery/lazy-static.rs

异步编程:无畏并发

并发 vs 并行

f37dd89173715d0e21546ea171c8a915_1440w.png

并发和并行都是对“多任务”处理的描述,其中并发是轮流处理,而并行是同时处理

并发的关键在于:快速轮换处理不同的任务,给用户带来所有任务同时在运行的假象。

线程局部变量

GitHub - Amanieu/thread_local-rs: Per-object thread-local storage for Rust

导致一些问题:

  • 竞态条件(race conditions),多个线程以非一致性的顺序同时访问数据资源
  • 死锁(deadlocks),两个线程都想使用某个资源,但是又都在等待对方释放资源后才能使用,结果最终都无法继续执行
  • 一些因为多线程导致的很隐晦的 BUG,难以复现和解决

并发模型

Rust 在设计时考虑的权衡就是运行时(Runtime)。出于 Rust 的系统级使用场景,且要保证调用 C 时的极致性能,它最终选择了尽量小的运行时实现。

线程(Thread)的概念,绿色线程 M:N 模型、需要较大的运行时来管理线程。

  • 当多个线程以不一致的顺序访问数据或资源时产生的竞争状态(race condition)。
  • 当两个线程同时尝试获取对方持有的资源时产生的死锁(deadlock),它会导致这两个线程无法继续运行。

spawn 创建线程,返回一个自持有所有权的 JoinHandle,调用它的 join 方法可以阻塞当前线程直到对应的新线程运行结束。

与 move 关键字配合使用,它允许你在某个线程中使用来自另一个线程的数据,跨线程的传递某些值的所有权

消息传递并发机制 Message Passing

线程或 actor 之间通过给彼此发送包含数据的消息来进行通信。

编程中的通道由发送者(transmitter)和接收者(receiver)两个部分组成。

当你丢弃了发送者或接收者的任何一端时,我们就称相应的通道被关闭(closed)了。

mpsc::channel 函数创建了一个新的通道(Multiple producer,single consumer)

tx.send(val).unwrap(); send函数会获取参数的所有权,并在参数传递时将所有权转移给接收者。

recv和try_recv。我们使用的recv(也就是receive的缩写)会阻塞主线程的执行直到有值被传入通道。一旦有值被传入通道,recv就会将它包裹在Result<T, E>中返回。而如果通道的发送端全部关闭了,recv则会返回一个错误来表明当前通道再也没有可接收的值。

try_recv方法不会阻塞线程,它会立即返回Result<T, E>:当通道中存在消息时,返回包含该消息的Ok变体;否则便返回Err变体。

mpsc :: Sender :: clone(&tx) 克隆生产者

共享内存的并发机制

多个线程可以同时访问相同的内存地址。

互斥体(mutex:Mutual Exclusion)

一次只允许一个线程访问数据。为了访问互斥体中的数据,线程必须首先发出信号来获取互斥体的锁(lock)。锁是互斥体的一部分,这种数据结构被用来记录当前谁拥有数据的唯一访问权。通过锁机制,互斥体守护(guarding)了它所持有的数据。

• 必须在使用数据前尝试获取锁。

• 必须在使用完互斥体守护的数据后释放锁,这样其他线程才能继续完成获取锁的操作。

调用它的lock方法来获取锁。这个调用会阻塞当前线程直到我们取得锁为止。

一旦获取了锁,我们便可以将它的返回值num视作一个指向内部数据的可变引用。Mutex是一种智能指针。更准确地说,对lock的调用会返回一个名为MutexGuard的智能指针。

  • 通过实现Deref来指向存储在内部的数据
  • 通过实现Drop来完成自己离开作用域时的自动解锁操作

原子引用计数 Arc, 需要付出一定的性能开销才能实现线程安全。

使用场景:可以将计算分割为多个独立的部分,并将它们分配至不同的线程中,然后使用Mutex来允许不同的线程更新计算结果中与自己有关的那一部分。

Sync trait and Send trait

std::marker 作为标签trait,Send与Sync甚至没有任何可供实现的方法。它们仅仅被用来强化与并发相关的不可变性。

只有实现了**::Send::** trait的类型才可以安全地在线程间转移所有权。除了Rc等极少数的类型,几乎所有的Rust类型都实现了Send trait.

任何完全由Send类型组成的复合类型都会被自动标记为Send.

只有实现了**::Sync::** trait的类型才可以安全地被多个线程引用.

用条件变量(Condvar)控制线程的同步

Thread Pool:改进服务器吞吐量

一块预先分配的线程,用于等待并随时处理可能的任务。

控制线程池数量,避免 DoS 攻击。

fork/join 模型、单线程异步 I/O 模型

Tokio M:N 模型

理解tokio的核心(2): task - Rust入门秘籍

Tokio

Setup | Tokio - An asynchronous Rust runtime

tokio 概览 - Rust语言圣经(Rust Course)

Tutorial | Tokio - An asynchronous Rust runtime

能力扩展

创业失败的破产工程师,我基本上算是没有面试经历,也没有参与太多的大型项目,所以我的思考可以辩证的来看,非经验之谈,而是一种思考框架。

审视如何做软件,如何做人,如何生活。

No LeetCode, no take-home assignments, etc.

到了一定年龄,专注是克服时间本身的唯一途径。

软件行业的细分岗位太多,我只探索了我想从事或者感兴趣的一些领域,有很多比如游戏开发,嵌入式开发我都兴趣较弱或者没有接触过,如果你感兴趣可以自行探索一下。

  1. 就业方向和能力拓展的前言内容提炼

  2. 数据库方向,资料整理和内容梳理

  3. AI + 区块链 + 金融科技,资料整理和内容梳理

  4. Web 全栈方向,资料整理和内容梳理

  5. 跨平台开发 IM,资料整理和内容梳理

有一定基础之后,在实习,学习语言,刷题或者做一些个人项目或者小型项目,这个同时可以进行一些专业知识的学习,手头的工作可能就是方向。

不过

**研究了与计算机科学相关的所有领域后,在软件行业中选择一个职业方向。**缩小选择范围,对你的职业规划大有帮助。尽量想得简单一些,因为软件行业本身已经非常复杂。

要懂得取舍很重要,减少欲望是一种高级的自律。

在职业发展和专业化方面,软件工程师有几种不同的选择。一些工程师更喜欢自己使用程序,而另一些则转向操作系统或数据库管理。这完全取决于您的背景和个人喜好-自然,软件工程的不同领域支付不同的薪水,但它们也有不同的要求。

从学以致用的角度出发来思考,无论是你是出于兴趣爱好,学业,或工作事业的目的。

拥有了不错的基本能力之后,该如何发展,大多数人可能是从选择公司开始被动的选择技术发展方向, 我们从技术趋势和兴趣出发来框定一些发展方向,再去选择公司和岗位也是不错的方式。

这个世界的发展需要什么,以及技术产业的发展现状,具有分布式计算、自然语言处理、机器学习、平台开发、网络或者系统设计方面的经验;

数据库领域/分布式数据库

云原生时代的数据库:过去、现在和未来|云端思享汇(第5期) 本期嘉宾: 李飞飞 阿里巴巴集团副总裁、达摩院数据库与存储实验室负责人 王建民 清华大学软件学院院长 安筱鹏 阿里研究院... - 雪球

2013****年,微软研究院首席研究员莱斯利·兰伯特(Leslie Lamport),因在提升计算机系统的可靠性及稳定性领域的杰出贡献荣获图灵奖。他的分布式计算理论奠定了这门学科的基础,并被称为“分布式计算原理之父”,他在1978年发表的论文《分布式系统内的时间、时钟事件顺序》成为计算机科学史上被引用最多的文献。

AI + 区块链 + 智能合约,金融科技 / 量化交易

跨平台开发 / IM + 多媒体 + WebRTC

Web 能力和服务端业务代码开发,全栈发展

其他的一些方向拓展,嵌入式

statistical and machine learning techniques to create scalable solutions for vehicle telemetry data and video analysis

you’re comfortable around the /sys and /dev directories and know how to get stuff done there.

数据的集成和连接工作

Proven experience in building Rust applications, ideally with some experience of running it on embedded systems or interfacing with hardware.

Things Are Getting Rusty In Kernel Land

对设计和某些专业有更深的理解,综合性人才会更出色。MUI’s material design components

数据库领域/分布式数据库

  1. 3 年以上工作经验,存储引擎研发经验者优先;
  2. 对时序数据库领域有所了解和研究,有系统性能调优经验

熟悉key-value/时序数据库

熟悉常见数据库如MySQL、Redis、ElasticSearch等操作

  1. 有从事分布式数据存储与并行计算开发经验。
  2. 对开源时序数据库(如 InfluxDB)有所了解,熟悉LSM、列存储等数据结构者优先;
  3. 了解常用的数据库如Mysql, redis, 了解LevelDB同学优先;
  4. 了解分布式存储理论,如IPFS;
  5. 熟悉levelDB等任意一种数据库存储引擎源码开发人员者优先
  • Experience in designing and implementing secure and highly-available distributed systems

Custom ETL design

Cache Infrastructure: Building large distributed systems, on-demand deployment of large-scale Redis and Memcached clusters;

  • Key-value store optimization skills.

Kafka, Postgres, Redis, InfluxDB,

密码学、信息安全与区块链

Blockchian for Identity Management

自主身份 Self-Sovereign Identity solutions 的必要技术:

  • Blockchain,cryptography and zero-knowledge proofs
  • Verifiable Credentials
  • Decentralized Identifiers

::What is blockchain?::

Distributed Ledger Technology (DLT, 分布式账本技术), commonly simply called “Blockchain Technology”, refers to the technology behind decentralised databases【分布式数据库】 providing control over the evolution of data between entities through a peer-to-peer network, using consensus algorithms【共识算法】 that ensure replication across the nodes of the network.

  • 不可篡改的性质
  • solve the double-spend problem of digital currency
  • considered a system with high Byzantine Fault tolerance

了解区块链的基本(第一部分):拜占庭容错(Byzantine Fault Tolerance) - SegmentFault 思否

了解区块链的基本(第二部分):工作量证明(PoW)和股权证明(PoS) - SegmentFault 思否

Permissioned Blockchain

Home - Sovrin

::What is Identity Management?::

Also known as “identity and access management”, or IAM, identity management comprises all the processes and technologies within an organisation that are used to identify, authenticate and authorize someone to access services or systems in that said organisation or other associated ones.

  • Identities need to be portable and verifiable everywhere, any time, and digitization can enable that.
  • Identities also need to be private and secure.

::Decentralized Identifiers (DIDs)::

Decentralized Identifiers are globally, unique and persistent(永久) identifiers.

They are entirely controlled by the identity owner 个人拥有数据

DID 独立于中央注册机构、权威机构或身份提供者。

Decentralized Identifiers (DIDs): A Beginners Guide!

A new spec is coming up in W3C:

Decentralized Identifiers (DIDs) v1.0

Image

::Verifiable Credential 可验证凭证::

Verifiable Credentials: The Ultimate Beginners Guide!

The Blockchain acts as a verifiable data registry 可验证的注册数据表

A “phonebook” that anyone can consult to verify what organisation a specific Public DID belongs to.

::In identity management::, a distributed ledger (a “blockchain”) enables everyone in the network to have the same source of truth (大家都拿到的相同的真实数据来源)about which credentials are valid and who attested to the validity of the data inside the credential(自证明身份的有效性), without revealing the actual data.(无需透漏实际的数据)。

主要角色 🎭:

  • identity owners :🆔 身份所有者
    • The identity owner can store those credentials in their personal identity wallet
    • use them later to prove statements about his or her identity to a third party (the verifier).
  • identity issuer: 身份颁发者
    • can issue personal credentials for an identity owner (the user).
    • the identity issuer attests to the validity of the personal data in that credential
  • identity verifiers:身份验证者

A Credential is a set of multiple identity attributes and an identity attribute is a piece of information about an identity (a name, an age, a date of birth).

凭证的有用性和可靠性完全取决于发行人的声誉/可信度。

Revocation means deleting or updating a credential.

How Identity Revocation on the blockchain works - Tykn

::Privacy and Security::

do not need to check the validity of the actual data, but can rather use the blockchain to check the validity of the attestation and attesting party

For example, when an identity owner presents a proof of their date-of-birth, rather than actually checking the truth of the date of birth itself, the verifying party will validate the government’s signature who issued and attested to this credential to then decide whether he trusts the government’s assessment about the accuracy of the data.

No personal data should ever be put on a blockchain.

What exactly goes on the Blockchain?

  • Only references and the associated attestation of a user’s verified credential are put on the ledger.
  • Public Decentralised Identifiers (Public DIDs) and associated DID Descriptor Objects (DDOs) with verification keys and endpoints.
  • Schemas: The formal description for the structure of a credential.
  • Credential definitions: The different (often tangible) proofs of identity or qualification issued by authorities
  • Revocation registries. 登记撤销
  • Proofs of consent for data sharing. 数据分享同意文件

Cryptography & Zero-Knowledge Proof

authentication: prove something about our identity – either our name, address or passport number.

A Zero-Knowledge Proof is a method of authentication that, through the use of cryptography

This is especially useful when and where the prover entity does not trust the verifying entity but still has to prove to them that he knows a specific information.

🔧 工具: 身份钱包应用和开放 API

Tykn’s APIs and Mobile SDK let you quickly integrate decentralized identity tech into your app or web-based user journeys. The Issuer & Verifier Web Portal and Mobile Wallet App you see in this demo are “off-the-shelf” components you can whitelabel if you prefer a no-integration approach.

The Mobile Wallet Demo - Tykn

Decentralized Identity Product Suite

Rreference

Blockchain Identity Management: The Definitive Guide (2021 Update)

Digital Identity and the Blockchain: Universal Identity Management and the Concept of the “Self-Sovereign” Individual

Blockchain for Digital Identity: Real World Use Cases | ConsenSys

Building A Trusted Identity: Blockchain ID Demo

Blockchain for Digital Identity | Accenture

Blockchain for identity management: Implications to consider

Elsevier Enhanced Reader

Decentralized Identity, Blockchain, and Privacy | Microsoft Security

Forrester

Jolocom launches new GDPR-compliant blockchain identity management app SmartWallet 2.0 - SiliconANGLE

国内:

Home | Nervos Network

DID分布式身份标识技术调研_九筒的博客-CSDN博客

分布式身份TDID_可信数字身份_可信数字凭证 - 腾讯云

聚链集团

太一公有云

数字身份链

区块链分布式身份服务解决方案_数字身份_身份授权_阿里云

分布式身份服务_TDIS_区块链服务_区块链开发-华为云

区块链分布式身份技术解密——重新定义你的“身份”管理-云社区-华为云

https://dl.brop.cn/wechat/DIDA/DIDA白皮书.pdf

IMG_0594.png

XMTP: The communication protocol and network for web3 and crypto

aligns incentives: 调整激励措施

pervasive:无孔不入

XMTP home

分布式系统与云

Distributed Systems

https://pdos.csail.mit.edu/6.824/

分布式系统

随着计算机在数量上的增加,计算机同样开始 分散。尽管商业公司过去愿意购买越来越大的大型机,现在的典型情况是,甚至很小的应用程序都同时在多台机器上运行。思考这样做的利弊权衡,即是分布式系统的研究所在,也是越来越重要的一项技能。

我们推荐的自学参考书是 Martin Kleppmann 的 《数据密集型应用系统设计》。与传统的教科书相比,它是一本为实践者设计的具有很高的可读性的书,并且保持了深度和严谨性。

对于那些偏爱传统教材,或者希望可以从网上免费获取的人,我们推荐的教材是 Maarten van Steen 和 Andrew Tanenbaum 所著的 《分布式系统原理与范型》(中文第二版英文第三版)。

对于喜欢视频课程的人,MIT 的 6.824 是一门很好的在线视频课程,由 Robert Morris 教授的研究生课程,在 这里 可以看到课程安排。

不管选择怎样的教材或者其他辅助资料,学习分布式系统必然要求阅读论文。这里 有一个不错的论文清单,而且我们强烈建议你出席你当地的 Papers We Love(仅限美国)。

关于 SaaS 的一切

核心的 SaaS API,多版本产品客户端,以及平台端的管理后台。

Enterprise-ready SaaS Boilerplate 基础框架调研: https://phab.xyz/T19 招聘系统(示例客户项目):https://www.craft.me/s/Xcplu3hEPHsxAh

SaaS 产品的技术挑战

SaaS presents developers with a unique blend of challenges: multi-tenancy 多租户, onboarding 客户生命周期管理, security 安全问题, data partitioning 数据分区, tenant isolation 租户隔离, and identity 身份认证.

基础服务:

  • send an SMS message
  • Tenant Onboarding

    • User Identity:associating each tenant with a separate Cognito User Pool
      • sign-in policies
      • standardized attributes or Add custom attribute
      • Tenant Connection Attribute
        • tenant_id (string, default max length, not mutable)
        • tier (string, default max length, mutable)
        • company_name (string, default max length, mutable)
        • role (string, default max length, mutable)
        • account_name (string, default max length, mutable)
      • configure password and administration policies,enable MFA
      • customize your user invitation messages
      • create app client id and secert
    • Identity Pool
      • Authentication Providers
    • Managing Users:Cognito API
    • Managing Tenants
      • Tenants must be represented and managed separate from users.
    • Onboarding
      • register:account & tenant & plan
      • check your email for the validation message that was sent by Cognito
      • login & change password
      • As a new tenant to the system, you are created as a Tenant Administrator.
  • Multi-Tenant Microservices

    • build a multi-tenant aware microservice:

Image.tiff

  • **Identity and Tenant Context:**our services need some standard way to acquire the current user's role and authorization along with the tenant context.
  • **Multi-Tenant Data Partitioning:**figure out how to partition, persist, and acquire data in a multi-tenant model.
  • Tenant Aware Logging, Metering, and Analytics
  • Isolating Tenant Data

    • create a set of IAM roles for each tenant.
    • With IAM policies, we can create rules that control the level of access a user has to tenant resources.
    • associate roles for tenants

面向的客户群体

  • 开发者
  • 中小企业客户

商业诉求的考虑

  • 利用开源力量,帮助客户在构建自己的 SaaS 产品时节省时间和金钱
  • 多个不同的客户端版本,轻松集成到客户自身的技术栈
  • 服务端核心的 API 集成(Rust),稳定、高效、安全、易部署,文档清晰
  • 长期的开源维护和技术支持,保证技术的稳定性
  • 国际市场和国内市场的大量第三方 API 集成

商业模式

  • 赞助 & 教程售卖 & 商业授权
  • 集成定制服务(报价制)
  • 提供云服务

基础功能

  • Server-side rendering for fast initial load and SEO
  • User authentication,第三方登录集成
  • 邮件通知(账户和交易)、Adding email addresses to newsletter lists
  • 文件上传和加载
  • Team creation, Team Member invitation, and settings for Team and User.
  • 日志服务定制
  • 网站数据分析
  • Production-ready, scalable architecture
  • 基础的 Web Component 组建
  • 订阅服务(支付平台集成,账单管理)
    • subscribe/unsubscribe Team to plan
    • update card information
    • verified Stripe webhook for failed payment for subscription.
  • Deploy 服务

产品框架

服务端

管理平台

数据层

客户端(Web)

代码架构

订阅服务的架构

Image.tiff

相似产品

GitHub - vercel/nextjs-subscription-payments: Clone, deploy, and fully customize a SaaS subscription application with Next.js.

GitHub - gmpetrov/ultimate-saas-ts: Template to quickstart a SAAS business

GitHub - bullet-train-co/bullet_train: The Open Source Ruby on Rails SaaS Template

GitHub - saasforge/open-source-saas-boilerpate: Free SaaS boilerplate (Python/PostgreSQL/ReactJS/Webpack)

WaVer - Free React template for building an SaaS or admin application

Wave - The Software as a Service Starter Kit built on Laravel & Voyager

GitHub - denoland/saaskit: A modern SaaS template built on Fresh.

GitHub - SimonHoiberg/saas-template: SaaS template for AWS, Amplify, React, NextJS and Chakra

GitHub - boxyhq/saas-starter-kit: Enterprise SaaS Starter Kit - Kickstart your enterprise app development with Next.js SaaS Starter Kit

GitHub - miracuthbert/saas-boilerplate: SaaS boilerplate built in Laravel, Bootstrap 4 and VueJs.

GitHub - saasitive/django-react-boilerplate: DIY Django + React Boilerplate for starting your SaaS

GitHub - jbarbier/SaaS_Memcached: Build your own Memcached SaaS

GitHub - go-saas/saas: go data framework for saas(multi-tenancy)

Pi Engine

GitHub - alectrocute/flaskSaaS: A great starting point to build your SaaS in Flask & Python, with Stripe subscription billing 🚀

GitHub - Saas-Starter-Kit/SAAS-Starter-Kit-Pro: 🚀A boilerplate for building Software-as-Service (SAAS) apps with Reactjs, and Nodejs

GitHub - dromara/lamp-cloud: lamp-cloud 基于Jdk11 + SpringCloud + SpringBoot 开发的微服务中后台快速开发平台,专注于多租户(SaaS架构)解决方案,亦可作为普通项目(非SaaS架构)的基础开发框架使用,目前已实现插拔式数据库隔离、SCHEMA隔离、字段隔离 等租户隔离方案。

GitHub - saasform/saasform: Add signup & payments to your SaaS in minutes.

关联产品

GitHub - Blazity/next-saas-starter: ⚡️ Free Next.js responsive landing page template for SaaS products made using JAMStack architecture.

GitHub - cruip/open-react-template: A free React / Next.js landing page template designed to showcase open source projects, SaaS products, online services, and more. Made by

参考资料

Books at builderbook.org

GitHub - aws-samples/aws-saas-factory-bootcamp: SaaS on AWS Bootcamp - Building SaaS Solutions on AWS

GitHub - RunaCapital/awesome-oss-alternatives: Awesome list of open-source startup alternatives to well-known SaaS products 🚀

Getting Started

Enterprise-ready SaaS Starter Kit | Security Building Blocks for Developers

潜在可以合作的工程师

goxiaoy - Overview

游戏引擎

Game engine Bevy

Games are a huge part of our culture and humanity is investing millions of hours into the development of games.

Bevy : data-driven game engine built-in Rust.

  • Capable: Offer a complete 2D and 3D feature set
  • Simple: Easy for newbies to pick up, but infinitely flexible for power users
  • Data Focused: Data-oriented architecture using the Entity Component System paradigm(shortened to ECS)
  • Modular: Use only what you need. Replace what you don't like
  • Fast: App logic should run quickly, and when possible, in parallel
  • Productive: Changes should compile quickly ... waiting isn't fun

Bevy - Introduction

Bevy - Introducing Bevy 0.1

ECS: Entities, Components, and Systems.

Entities are unique "things" that are assigned groups of Components, which are then processed using Systems.

epresent globally unique data using Resources.

Bevy - Assets

bevy/examples at latest · bevyengine/bevy

Bevy - A data-driven game engine built in Rust

RG3D

Fyrox

GODOT

Introduction

godot-rust

ArtStation - Making Of Minimoys Procedural Wall

Download the latest indie games

Learn OpenGL, extensive tutorial resource for learning Modern OpenGL

Homegrown rendering with Rust

OpenGL

Basics - Learn OpenGL Rust

Rust-and-opengl-lessons - Collection of example code for learning OpenGL in Rust | RustRepo

2D Level Editor

LDtk

GitHub - katharostech/bevy_retrograde: Plugin pack for making 2D games with Bevy

Introduction — Tiled 1.8.2 documentation

Tales of Yore

Fyrox : feature-rich, general purpose game engine

**classic OOP:**complex objects in the engine can be constructed using simpler objects.

  • Base - a node that stores hierarchical information (a handle to the parent node and a set of handles to children nodes), local and global transform, name, tag, lifetime, etc. It has self-describing name - it is used as a base node for every other scene node (via composition).
  • Mesh - a node that represents a 3D model. This one of the most commonly used nodes in almost every game. Meshes could be easily created either programmatically, or be made in some 3D modelling software (like Blender) and loaded in your scene.
  • Light - a node that represents a light source. There are three types of light sources:
    • Directional - a light source that does not have position, only direction. The closest real-world example is our Sun.
    • Point - a light source that emits light in every direction. Real-world example: light bulb.
    • Spot - a light source that emits light in a particular direction with a cone-like shape. Real-world example: flashlight.
  • Camera - a node that allows you to see the world. You must have at least one camera in your scene to be able to see anything.
  • Sprite - a node that represents a quad that always faced towards a camera. It can have a texture, size, it also can be rotated around the "look" axis.
  • Particle system - a node that allows you to build visual effects using a huge set of small particles, it can be used to create smoke, sparks, blood splatters, etc. effects.
  • Terrain - a node that allows you to create complex landscapes with minimal effort.
  • Decal - a node that paints on other nodes using a texture. It is used to simulate cracks in concrete walls, damaged parts of the road, blood splatters, bullet holes, etc.
  • Rigid Body (2D) - a physical entity that is responsible for dynamic of the rigid. There is a special variant for 2D - RigidBody2D.
  • Collider (2D) - a physical shape for a rigid body, it is responsible for contact manifold generation, without it any rigid body will not participate in simulation correctly, so every rigid body must have at least one collider. There is a special variant for 2D - Collider2D.
  • Joint (2D) - a physical entity that restricts motion between two rigid bodies, it has various amounts of degrees of freedom depending on the type of the joint. There is a special variant for 2D - Joint2D.
  • Rectangle - a simple rectangle mesh that can have a texture and a color, it is a very simple version of a Mesh node, yet it uses very optimized renderer, that allows you to render dozens of rectangles simultaneously. This node is intended to be used for 2D games only.

fyrox - Fyrox Cheat Book

协作:代码与项目管理

尝试把目前代码管理和项目协作的工具进行使用层面的总结,在这个过程中也有查阅网络上其他人的分享和思考,希望这篇文章的内容能对你的工作有所助力。

首先说明一下自身的情况,之前运营过一家小型的软件外包企业,现在的工作基本以自由职业为主,所以我的需求主要是对客户项目、团队内部的项目以及个人的兴趣项目进行管理,主要会涉及以下内容:

  • 项目的立项推进
  • 需求沟通、开发任务迭代
  • 代码管理和发布
  • 团队内部知识库、工具流程的建设

这些经验可能对一些小型软件公司亦适用,或许可以作为评估选型的参考。

代码托管是件简单而复杂的事情,软件行业的很多问题都来自于规模性和复杂度,这也是现实世界的一种映射。我们需要在工具成本、易用性、灵活性、网络状况(还有一些政治因素考虑,最近几年的世界演化,技术无国界并不是一个物理规律)、是否开源、私有化部署的复杂度等等层面做些考虑。

大型技术组织通常有自己代码管理工具体系(以及历史),有非常多的构建发布工具集成、和对某些云服务的集成度,不过我们更关注的是一个工程师个人工作或者小型团队的选择,暂时不考虑这些因素。

避免知识的诅咒

为了避免认知偏差,或信息不对称的影响,快速阐述几个基本的概念:

1)版本控制系统 VCS(Version Control System)

版本控制(抑或是源码管理 Source Code Management)是软件团队为了追踪和管理代码变化的软件工程实践,相应的版本控制软件可以帮助我们在出现错误的时候回滚之前版本,减少对其他人的工作干扰,是团队协作开发、DevOps 团队构建部署发布流程不可或缺的基础设施。

没有版本控制绝对是一种噩梦般的体验,想一想电脑上无数人工版本记录嵌套文件夹 📁

常见的版本管理工具:

  • 分布式 Distributed VCSGitGit-LFS(Git extension for versioning large files)Mercurial 以及 Darcs,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来,代码工作可以离线、独立/分布式协作;
  • 集中化版本管理 Centrallized VCS: 诸如 CVSSubversion(Apache Project) 以及 Perforce(Not Free),单一集中管理的服务器,保存所有文件的修订版本,而协同工作的人都通过客户端连到这台服务器,取出最新的文件或者提交更新,超大型的项目或者游戏开发有很多会用 Perforce 来管理;

2)Git 的诞生及相关工具

2005 年,因为和托管 Linux 内核代码的商业版本控制系统软件公司 BitKeeper 在版权上出现纠纷,Linux Torvalds 花了两周时间用 C 语言写了一个全新的版本管理系统,在一个月内完成了 Linux 内核代码的迁移,并向全世界免费开源了这个系统 Git,这是历史的偶然性

Git

脑图在线文档:https://www.mubucm.com/doc/oYizknWrD7

以项目为起点,代码为资产

我们不需要用一个工具来满足所有的需求,不同的项目对代码管理的要求不一样,某些客观和非技术原因也会影响决策,所以从项目类型出发来思考代码管理的具体实践是可行的。

虽然软件代码不像具体的实业生产一样不可逆,可以经常迭代重构,但是把代码当作最重要的资产,这个思维最好从一开始就具备,可以帮助下意识的考虑很多事情:

  • 将代码视为自己的资产,好比建立一所长久居住的建筑,你会很在意质量是否可靠、长期是否可维护,是否在将来的某些情况可以轻松扩展
  • 资产的保值也不得不考虑,这些代码是否可以给其他人或者世界带来跟多的价值;
  • 代码资产的安全性亦不能被忽略(丢失、泄漏或授权等等问题);

从这两个因素的考虑,我绘制了一下代码管理的决策过程 👇,后面对重要部分进行解释。

Code

开源项目的运作

这篇文章并不是来谈论开源项目如何运作的,如果以后经验丰富了再写篇文章来分享。

无论是个人还是商业项目,是否要开源运作,取决于个人对开源事业的一个看法和兴趣,而且开源也不是万能的,不过有很多伟大的软件确实无法在工作或者商业项目中诞生

对于开源项目来说,熟悉 Git / GitHub 的使用,融入社区、从大量优秀的开源项目和工程师分享中学习和成长,到慢慢参与为开源世界最贡献,你会发现真正的热爱,以及开源提供的不可替代的价值。

是否私有化部署

对于内部项目来说,出于对代码安全、访问速度、成本等因素的考虑,除了选择代码托管平台(一般是根据人数和性能限制来收费),自建 Git Service 是一个不错的选择,可以根据实际情况降低成本和灵活集成其他服务、精简功能,对人员和项目代码资产的控制也有全部自主权,不过要付出一些必要的平台维护成本和精力。

对于我自己的工作场景来说,还会遇到开发者人数经常跟项目进展剧烈波动的情况,代码托管平台的收费模式会有一定困扰,功能繁杂也会增加培训成本,自建 Git Service 可以减少这些层面的麻烦。

大型的软件企业和机构一般都会选择给自己提供云服务的厂商,在云上或者内网进行私有化部署整套产品研发流程产品体系(包括代码管理这部分),来保障研发效率和代码安全。

所以个人开发者或者小型团队,如果有能力的话,自建 Git Service 是各方面权衡下来可以做的一件事情,付出的成本主要是服务器和带宽的费用,而且也有很多开源软件来协助做这件事情。

私有化部署 Git Service 的免费开源软件推荐:

我的选择:Phabricator + Gerrit Code Review

我们在工作中首先遇到的问题是多代码仓库源同步的问题:

  • 由于网络联通和拉取速度的问题,需要把 GitHub 上面参与的项目或者关注学习的开源项目进行镜像同步,提升使用体验;
  • 团队内部的软件项目,根据不同的项目开发状态,对代码评审的要求不一样,我们通过两种不同的工具来进行思维和实践的区分,这里对团队来说会增加不少学习成本,需要注意 ⚠️。
  • 外部客户的代码经常存放在不同的代码托管服务商,在协作过程中,我们对于需要交付的代码部分会同步到 Phabricator 的 Git Server 上面,方便我们查阅和管理。

代码提交工作如何和项目进度、问题追踪系统无缝衔接,Code Review 工作如何开展?这些问题也非常重要,所以我们的具体策略是这样:

  • 低成本采购 1~2 台云服务器,需要 https + CDN 的支持,做好数据备份和安全维护;
  • 搭建两套软件服务:
    • Gerrit Code View 提供了一套严格的 Code Review 机制,可以协助团队落地代码审阅,它本身也包含了一套代码仓库管理的机制,亦可以把它当作一个简单的 Git Service 来使用;
    • Phabricator 其实包含一大堆用来进行软件开发管理的应用,有点像瑞士军刀一样。虽然该项目在 2021.6 月份停止了维护,但是足够成熟,还可以继续用,如果你对 PHP 很熟悉,还可以 Fork 一份自己来学习维护;我们主要用下面的模块:
      • Diffusion 代码仓库管理,代码提交与任务可以自然的关联,提升沟通和开发效率;
      • **项目管理:**项目立项、成员管理、背景资料和知识的留存,任务管理,进度和问题追动,看板系统是开发过程中不可或缺的一部分。
      • 知识分享:也把它当作一个简单的博客/知识库工具来用;
  • Phabricator 启动了一个 OAuth2 Server 来让 Gerrit 可以打通用户登陆,Gerrit 的 Change Review 进展也可以通过集成和功能扩展 Phabricator 的任务面板来。
  • Phabricator 也可以与邮件、即时通信软**件(Slack、飞书)**进行集成;
  • 在不超过一定人数规模的情况下,两个软件的使用体验和速度都是绝佳的;

我们选择 Phabricator 来作为代码管理的入口,有一些我们自己的使用经历和主观判断,如果觉得不合适,也可以考虑用自己喜欢的项目管理软件来替代(能私有化部署最好),而且很多功能齐全的代码托管平台本身会把这些功能都整合进去,是否好用,就需要你实际体验一下了。

我们团队面板:https://phab.xyz

这里还遗漏了两块比较重要的部分:**代码管理策略和 DevOps(CI/CD 等等)**的内容,改日再聊。

调试与测试

远程调试:https://github.com/HuolalaTech/page-spy-web

🔐 安全

自动化检查项目中是否有安全密钥泄漏风险:

  • https://github.com/sirwart/ripsecrets

参考资料

工作流程

公司使用 Git 作为源码管理系统,托管在[[ https://dev.tencent.com/ | 腾讯开发者平台(Coding) ]],不可避免涉及到多人协作。协作必须有一个规范的工作流程,让大家有效地合作,使得项目井井有条地发展下去。"工作流程"在英语里,叫做"workflow"或者"flow",原意是水流,比喻项目像水流那样,顺畅、自然地向前流动 ,不会发生冲击、对撞、甚至漩涡。

公司建议使用 Sublime Merge 工具来进行 Git 工作流程的管理。如果你喜欢或者使用过 Sublime Text,那肯定也会喜欢 [[ https://www.sublimemerge.com/ | Sublime Merge ]]。

Image

在了解具体的流程说明之前,需要先了解一下Git Workflow 的基本原则

"功能驱动式开发"(Feature-driven development,简称FDD)

它指的是,需求是开发的起点,先有需求再有功能分支(feature branch)或者补丁分支(hotfix branch)。完成开发后,该分支就合并到主分支,然后被删除。

学习使用 / Learn

虽然说使用 Sublime Merge 工具之后,可以不需要掌握太多 git 命令行,但是基础的概念和命令还是必须完全掌握,有助于理解后续的工作流程说明和紧急状况使用。

基础术语

  • Workspace : 工作区
  • Index / Stage : 暂存区
  • Repository :仓库区
  • Remote :远程仓库
  • Branch :分支

常用命令

Image

一般来说,只要掌握以上的基本命令,就可以满足日常使用,其他的命令用的到时候查阅文档就可以,使用多了自然会熟练起来,不用担心记不住命令。

入门选手请查阅:

分支管理策略

[[ https://nvie.com/about/ | Vincent Driessen ]] 提出了一个[[ https://nvie.com/posts/a-successful-git-branching-model/ | 分支管理的策略 ]],我觉得非常值得借鉴。它可以使得版本库的演进保持简洁,主干清晰,各个分支各司其职、井井有条。

Image

公司也采用这种分支管理策略。

常设分支:

  • master 主分支:所有提供给用户使用的正式版本,都在这个主分支上发布。
  • develop 开发分支:日常开发使用,最新的开发进度分支。

临时性分支:

  • Feature 功能分支;
  • Release 预发布分支;
  • Bugfix 缺陷修复分支;
  • hotfix 线上缺陷紧急修复分支。

Pro Git

深入了解 Git 原理和相关操作解释。

团队协作规范 / Git Workflow

团队开发中,遵循一个合理、清晰的 Git 使用流程,是非常重要的。 这部分的内容是 Yousails Software 团队内部使用的 git 协作流程说明。

仓库维护要则

  • 必须避免在代码仓库中带入与环境、开发配置、生产编译结果相关的文件;
  • 代码合并后需要及时删除无用的本地和远程分支;
  • 非紧急情况下,不要再 develop 或 master 分支下工作,提交代码;
  • 定期 rebase,合并上游代码变化;
  • 学会使用 PR 来进行 Code Review 和问题讨论、寻求帮助;(在 LKB 系统上进行)

开发阶段(项目未对外发布)

在开发阶段整个开发协作流程采用 GitHub Flow 规范:

Image

Understanding the GitHub flow: https://guides.github.com/introduction/flow/

流程说明

1 )只有两个长期分支 master 和 develop,master 分支作为预发布环境(真实用户测试环境 stage 环境),需要和 develop 分支保持一致,develop 分支作为开发环境的代码,永远是可发布状态。

master 和 develop 分支需要设置分支保护,只有有权限的人才能直接推送代码; 2 )如果有新功能开发,可以从 develop 分支上检出新分支。 3 )在本地分支提交代码,并且保证按时向远程仓库推送代码。 4 )当需要反馈或者帮助,或者需要合并分支时,需要发起 pull request。

PR(Pull Request)是 Github Workflow 伟大的发明,不仅可以用来合并分支,还可以用来问题讨论、Code Review。 5 )当 review 或者讨论通过后,代码会合并到目标分支(develop)。 6 )当完成 Sprint 开发任务或需要向 stage 环境交付代码时,需要合并 develop 分支代码到 master,发布给用户。

开发新功能

基于 develop 分支的最新代码创建功能分支(也可以成为本地分支):

# Write a feature
git checkout develop
git pull
git checkout -b <branch-name>

# 分支名建议具备一定的描述性,比如
# refactor-authentication、user-content-cache-key、make-retina-avatars

经常性 rebase 合并其他人的修改,保证和上游分支一致:

git fetch
git rebase origin/develop

当功能开发完毕并且通过了测试,提前之前最好进行一次 rebase,解决潜在的代码冲突,然后提交所有修改记录到本地仓库:

git add --all
git status
git commit --verbose

# Commit 提交信息示例
完成订单自动同步 ERP 功能

- 修改订单数据接口
- 完成 ERP 接口对接和调试
- 完成订单状态的同步

Link: lkb.work/T328

功能分支开发完成后,很可能会有一大堆 commit,有些其实并没有参考价值,合并到主分支的时候,往往希望只有一个(或最多两三个)commit,这样不仅清晰,也容易管理。

# 交互式的合并 commit 信息
# https://help.github.com/articles/using-git-rebase-on-the-command-line/
git rebase -i origin/develop

推送代码到远程仓库(代码推送之前建议进行一下 rebase 操作,保持与 develop 分支同步)

# 加上 force 参数,因为 rebase 以后,分支历史改变了,与远程分支不一定兼容,可能要强行推送
git push --force origin <branch-name>

在代码托管平台中,提交合并请求 ,在项目沟通群众告知相关人员进行代码 Review 和合并部署操作。

Coding 提供了可以通过命令行创建 Merge Request 的方法:

git push origin <branch-name>:mr/develop/<branch-name>

# 自动创建的使用最后一个『commit message』作为标题和内容
# 可以使用 @Seaony 自动关联相关人员为评审者
# 自动 # 加为关联资源

审阅代码 / Code Review

使用命令行或 Sublime Merge 都可以进行代码修改变动的比较和查看。代码层面的意见反馈和讨论,可以直接在代码托管工具和即时通讯工具上面进行。

Image

编码标准

https://learnku.com/docs/laravel-specification/5.5

https://learnku.com/docs/psr

https://learnku.com/articles/25020

Code Review 指南

Thoughtbot:https://github.com/thoughtbot/guides/tree/master/code-review

基本原则

  • 认同大多数编码决策与观念有关,权衡利弊,快速确定解决方案
  • 提好问题,而不是提请求(提问的智慧)
  • Ask for clarification. ("I didn't understand. Can you clarify?")
  • Avoid selective ownership of code. ("mine", "not mine", "yours")
  • Avoid using terms that could be seen as referring to personal traits. ("dumb",
  • "stupid"). Assume everyone is intelligent and well-meaning.
  • Don't use hyperbole. ("always", "never", "endlessly", "nothing")
  • 不要讽刺别人
  • 如果有太多其他人不理解的地方,或者讨论篇幅过长,可以进行同步讨论(聊天室、语音会议、屏幕分享等)

自己的代码被审阅

  • 友好的回复审阅者的建议(建议不错,我去修改一下)。
  • 对事不对人,如果看到咄咄逼人或者带有个人色彩的评论,如果可以的话,及时在线问清楚对方的意图,不要自我揣测。
  • Assume the best intention from the reviewer's comments.
  • 保持代码的可读性(让代码自己解释为什么存在的原因)
  • Push commits based on earlier rounds of feedback as isolated commits to the branch. Do not squash until the branch is ready to merge. Reviewers should be able to read individual updates based on their earlier feedback.
  • 及时回复审阅建议
  • Wait to merge the branch until continuous integration (TDDium, Travis CI, CircleCI, etc.) tells you the test suite is green in the branch.
  • Merge once you feel confident in the code and its impact on the project.

审阅他人的代码

审阅者必须先理解 PR 的上下文(修复缺陷、提升用户体验、重构已有代码);

  • Communicate which ideas you feel strongly about and those you don't.
  • 找出更简化的代码实现方式,并提供建议。
  • 如果需要讨论的问题过于学术化,或者架构的调整,线下的下午茶讨论或许更合适。
  • 提供替代的实现方案时,但是假设提交者已经考虑了他们(“你觉得在这里使用自定义验证器怎么样?”)。
  • Seek to understand the author's perspective.
  • Sign off on the pull request with a 👍🏾 or "Ready to merge" comment.
  • Remember that you are here to provide feedback, not to be a gatekeeper.

合并分支 / Merge Request

合并分支的时候,避免选择 Fast-Forward 合并方式,而是策略合并,策略合并会让我们多一个合并提交。这样做的好处是保证一个非常清晰的提交历史,可以看到被合并分支的存在。

拥有合并权限的开发同学,也可以通过命令行进行合并:

git log origin/develop..<branch-name>
git diff --stat origin/develop
git checkout develop
git merge <branch-name> --ff-only
git push

已经合并的功能分支需要及时进行删除

# delete remote feature branch
git push origin --delete <branch-name>

# delete local feature branch
git branch --delete <branch-name>

迭代阶段(项目已上线使用)

此部分内容待更新,该流程主要区别在于命名和分支管理方案上与开发阶段有所差异:

  • 功能开发
  • 缺陷修复
  • hotfix 流程
  • 预发布流程
  • Issue 追踪管理

团队使用 Phabricator 进行缺陷追踪和管理。

参考资料

解决问题:工程师思维

大多数情况,软件工程师的形象都是在电脑屏幕前实现某个产品的或系统,或者去解决某个棘手的技术问题, 但是这并不是这个工作的全部。

与程序员的区别

软件工程师的称呼意味着:你不能仅仅只是会写代码。

这个岗位称呼的差异其实也等同于说**「编程」和「软件工程」是两回事,「编程」指编写代码的活动,而「软件工程」则是在此基础上再添加时间这一维度**。正如三维的立方体不等同于二维的正方形,距离不等同于速度,「软件工程」也不等同于「编程」。

很多人认为程序员和软件工程师是同一职位的不同的称呼,我在这里并没有使用程序员这个称呼,因为在我看来两者有着本质的区别:

内在驱动的差异

工程师靠兴趣、好奇心和对事物的热爱内在驱动,主动积极前进,而不是工作的压力和薪酬的激励,工作的过程就是实现自我的方式。非工作狂,这是另外的话题

有人写程序的动力是获取成绩、金钱、升迁,这样的动机下,编程工作会变的枯燥无味,在工作中只喜欢简单/容易的任务,最好可以直接复制粘贴,改下能过关就可以开心下班,下班后也不想多看一眼代码。

有时候为了生活,不得不这样,并不是每个人写程序都会一腔热血,他的人生目标也不是成为一个伟大的工程师,写程序仅仅是谋生的手段,这样没有问题,尊重每个人的选择,但是寻找一份热爱并可以长期投入的职业真的是很幸运的事情,我觉得应该努力去尝试找到它。

不过对于很多公司来说,程序员/工程师/开发者的称谓并没有区别,所以关键还是:自我要求和追求目标的差异,所以也不用过于纠结称呼的区别。许多人的职业道路都是从学校或者小作坊进入大公司或是早期创业公司,在这个过程中大概率会经历从「程序员」到「软件工程师」的角色转变。

具备工程师思维

开发人员:“你看,在我这里是好的” --- 同事: 😓

写一段在 ”本地运行没有问题” 的代码是很容易,软件行业的难度在于规模和复杂性问题

工程层面关注的是 ”为了什么“、”怎样实现“、”评价好坏的标准是什么“。把软件开发纳入工程范畴,意味着不仅要考虑软件功能的实现,还要考虑:

  • 与基础理论的关系(比如数学、算法、计算机体系架构、存储/网络等理论)
  • 与生产过程的关系(软件的构建环境、发布流程等)
  • 与人的关系(用户/开发人员,需求管理,工作协同)
  • 与成本的关系(不仅是实现、维护的人力成本,还有资源的消耗/维护成本等等)

所以,工程师思维可以简单归类:

  • 拥有技术思维和视角:有架构设计和交付能力,性能因素亦不可忽略;
  • 产品思维:产品或服务的起源是用户价值的创造;
  • 工程思维:构建产品本身的流程机制、工具链和文档和社区亦不可或缺;
  • 成本意识:在实现目标价值的情况下,开源节流;
  • 解决问题的能力:把未知问题转化为已知问题,大问题分解为小问题,模块化系统思维;
  • 懂得取舍(Trade Off),在多维度中寻找平衡;

📌 关于工程思维更通俗的解释,这篇文章是极好的,有点长,你可能需多点耐心:

  • 工程思维:https://mp.weixin.qq.com/s/Wlvh53qI8ugUoQ2eSwOPdQ

题外话:“贬义” 的工程师思维

软件行业,工程师思维有时候会是贬义词,这个研发团队与产品最终用户之间的矛盾有关系,用户经常抱怨软件工程师不说人话,做出来的系统不人性化,不符合直觉,完全是工程师思维。

如果工程师的心思只想尽快交差了事,做事的时候不主动思考,再好的产品开发流程也没有办法覆盖所有细节,在这种开发环境中做出来的东西只是一堆功能的拼凑,与公司的价值观和经营理念也有很大关系。

对于工程师个人来讲,工作态度的不同很大程度上来自于自我驱动的差异

软件工程师的自我修养

希望上面的文字有助于帮你构建「如何成为软件工程师」的职业原则与行动指引。

软件工程师能够创造出一些特别出彩的东西——无论是从技术角度还是商业角度去看。在兴趣驱动下,更愿意积极主动的花费时间深入研究技术、或某种工具、语言等。更渴望知道 “Why”,不盲从,有质疑和反叛精神亦是软件工程师的底色(黑客精神)。

在我看来,软件工程师是连接商业世界和代码世界的桥梁,而不仅仅一个闷头搞代码的搬运工。一个软件工程师的职业生涯非常重要的一步就是不再想着“我擅长什么语言”,而是开始考虑 “什么方式(编程语言、工具 🔧 等)最适合解决这个特定项目和客户的问题?”

同时,软件工程师有更多的工作选择灵活度,如果你不仅是非常优秀的软件工程师,而且还很有商业洞察和分析能力,那你还是非常稀缺的初创公司合伙人的绝佳人选,亦或者对编程技术这条道路兴趣盎然,那就寻找机会、继续深造往技术专家/架构师层面前进。

从程序员到工程师,以及到更高的职业目标,这个过程并不能一蹴而就。如果你想保持一个较长的职业生涯,并把当作终身事业来对待,这意味着你要保持持续学习和阅读、折腾的习惯,这点需要一定过的心理准备。

以此自勉。

基本素养和“软“技能

这部分的内容并不是标准答案,这里将软件工程师该拥有的基本素养和“软“技能进行汇总阐述。

持续学习

软件行业的变化速度对于持续学习有极高的要求,你的学习动机一定要非常清晰,升职加薪没有问题,但是如果缺少更深层次的动机,你很快就会对变化产生无力感,如果你有很强烈的解决问题/创造物品的原动力,为了解决问题你在持续的学习和深入,这种热情和学习动力会持续更久,而你也逐渐会成为知识的驾驭者,持续学习会从态度转化为一种基本能力

持续学习不是指无脑的学习大量知识,而是你逐渐认清自己的兴趣和能力范畴,有目标的持续深入学习,拓展能力边界,这对很多人来说是一个痛苦且漫长的过程。

逻辑分析和抽象思考

探究事物本质和如何运转的原理,把代码看作自己非常重要的资产,有持续学习习惯;

在学习和模仿的过程中,逐渐拥有优秀的逻辑分析能力,能够对业务逻辑进行合理的抽象和拆分;

随着工作和项目经历的丰富,你对如何组织代码和设计业务架构逐渐有所得,逐渐面对软件行业很多规模性、复杂度、高可用性的问题,你在学习如何权衡决策的艺术。

工具思维

人类发展的历史,也是人类如果使用工具导致社会进步的历史。

有时候我发现,无论在我们个人生活或者工作中,我们其实都是在寻找各种工具、软件来解决问题,最后如果实现找不到我们就自己造一个,极端的情况,我们在寻找工具的路上越走越远,忘记了最初要解决的问题。

工具思维的简单总结就是需要意识到并理解工具可以带来重要的作用,教会别人捕鱼很重要,但是让制造一个工具,让不会钓鱼的人也可以轻松捕鱼价值更大,但是不被工具细节束缚或盲目选择。

在生活和工作中,工具思维有助于帮我们我们行程一些做事的方法论或决策流程(框架),对于工程师来说,追求卓越的工程实践,自动化事物需要工具思维的帮助。

如果最后你能深刻意识到,思维本身亦是一种工具,你对工具思维的理解可以说非常到位。

协作与沟通

可能有人觉得程序员不需要和别人沟通,一个人专心写代码就可以工作,这绝对是个误解,对于程序员可能会少一些面对面的沟通,但是在工作中,与他人沟通绝对是非常大的一块内容。

  • 较好的英文阅读能力,能够使用英语进行日常工作及邮件、文档编写;
  • 团队合作和业务理解能力,如何去沟通和理解需求,如何对任务的进行反馈、组织对细节问题(需求 or 技术)的研讨和分享,都要求工程师有良好的书面和语言表达能力;
  • 代码 Review 更是强调参与、沟通,协作和沟通是否做好,决定了团队整体的技术氛围;

抛出政治和民族主义考虑,我还是很希望未来是一个技术无国界的世界,大家都可以参与开源社区的构建,能随时阅读和学习优秀的开源系统代码,这些可以让我们和全球有趣的小伙伴一起创造,希望未来不要太糟糕。

文档能力

文档和演讲时知识输出和学习非常重要的两种方式(一种总结能力的体现)。

但是很多工程师的文档能力很糟糕,我目前不太清楚这块是个人性格、能力的问题,还是整个企业工程师文化导致的,可能两者都有。

解决问题的能力

你身边有些朋友看起来非常能解决问题,无论是简单还是复杂,他们总是能热心的投入精力,探索各种方法,最后找到问题的解决方案,这种能力是天赋吗?有几个方面的原因:

  1. 你是否停止了思考和学习?或者搜索引擎已经完全让你放弃了主动尝试解决问题的欲望;

  2. 经常折腾和实践必不可少,动手能力是前提,很多知识实践之后才会成为你的;

  3. 作为工程师,要学会正确向其他人提问和对经验进行总结(Note or Blogs);

    你清楚的知道问题的本质是什么,一般也就有了解决方案。

  4. 要解决复杂问题,学会分层或者分模块拆解思考,基础知识扎实的重要性也在这里体现;当然知识的广度也非常重要;

  5. 学会分享见解和帮助他人;

最后,保持一个健康的身体和强健的体魄是必要的。

参考资料