人月神话

https://github.com/youngqqcn/expert_readed_books/blob/master/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/%E4%BA%BA%E6%9C%88%E7%A5%9E%E8%AF%9D.pdf

  • 开发成本随着人数和时间的不同,有很大的变化。

  • 用人月来衡量工作是非常危险的,因为它建立在人与人之间不需要相互交流的前提

  • 在错综复杂的任务中,沟通和交流的工作量非常大,如果添加更多的人手,实际会延长进度

  • 不要进度一落后,就想着加人

  • 少数人思考确定系统的方向,多数人来解决实际实现问题

  • 巴比伦塔为什么失败?

    • 两个方面——交流,以及交流的结果——组织
    • 鼓励网形交流,而不是树形
  • 团队组织的目的是减少不必要交流和合作的数量,因此良好的团队组织是解决上述交流问题的关键措施。 减少交流的方法是人力划分(division of labor)和限定职责范围(specialization of function)

  • 每棵子树所必须具备的基本要素:

    • 任务(a mission)
    • 产品负责人(a producer)
    • 技术主管和结构师(a technical director or architect)
    • 进度(a schedule)
    • 人力的划分(a division of labor)
    • 各部分之间的接口定义(interface definitions among the parts)
  • 产品负责人的角色是什么?

    • 他组建团队,划分工作及制订进度表。他要求,并一直要 求必要的资源。这意味着他主要的工作是与团队外部,向上和水平地沟通。他建立团队内部 的沟通和报告方式。最后,他确保进度目标的实现,根据环境的变化调整资源和团队的构架。
  • 技术主管的角色是什么?

    • 他对设计进行构思,识别系统的子部分,指明从外部看 上去的样子,勾画它的内部结构。他提供整个设计的一致性和概念完整性;他控制系统的复 杂程度。当某个技术问题出现时,他提供问题的解决方案,或者根据需要调整系统设计。用 Al Capp 所喜欢的一句谚语,他是“攻坚小组中的独行侠”(inside-man at the skunk works.)。 他的沟通交流在团队中是首要的。他的工作几乎完全是技术性的。
  • 产品负责人和技术主管是同一个人

    • 这种方式非常容易应用在很小型的队伍中,可能 是三个或六个开发人员。在大型的项目中则不容易得到应用。原因有两个:
      • 第一,同时具有 管理技能和技术技能的人很难找到。思考者很少,实干家更少,既是思考者又是实干家的太少了。
      • 第二,大型项目中,每个角色都必须全职工作,甚至还要加班。对负责人来说,很难在承担全部管理责任的同时,还能抽出时间进行技术工作。对技术主管来说,很难在保证设 计的概念完整性,没有任何妥协的前提下,担任管理工作。
  • 如何根据一个严格的进度表来控制项目?

    • 第一个步骤是制订进度表。进度表上的每一件事,被称为“里程碑”,它们都有一个日期。
      • 里程碑的选择只有一个原则,那就是,里程碑必须是具体的、特定的、可度量的事件,能够进行清晰定义
    • 保持进取心, 必须关心每一天的滞后,它们是大灾祸的基本组成元素
    • 及时反馈问题
      • 减少角色冲突: 老板只了解信息,不越俎代庖,避免经理失去权威;经理及时上报,并且提出解决方案,让老板安心。
      • 有了解真相的评审机制。通过关键路径图、明确里程碑、周会多方面来评审是否存在问题。
  • 项目管理主要是对人的关注和管理

代码大全

https://github.com/youngqqcn/expert_readed_books/blob/master/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/%E4%BB%A3%E7%A0%81%E5%A4%A7%E5%85%A8.pdf

《代码大全》——第三十一章 个人性格

  • 谦虚

    • 那些最精通编程序的人往往是那些认为自己的头脑是多么有限的人,他们是谦虚的。而那些最为糟糕的程序员往往是那些拒绝承认自己的能力不适应工作任务的程序员。他们的自我妨碍自己成为优秀程序员,你学到越多的东西来弥
      补你的大脑,你就越能成为一个好的程序员,你越谦虚,你取得的进步也就越快。

    • “分解”一个系统的目的是为了使其更为简单易懂。人们往往易于理解几条简单的信息而不是一条复杂的信息。所有软件设计方法的目的是将复杂的问题分解为简单的几部分,不论你是否使用结构化、自顶向下或是面向对象的设计,以上目标都相同。

  • 好奇心(学习)

    • 如果正在开发有良好市场前景的软件,你所学的一半知识将会在今后三年内过时,如果你不再学习新知识,你将会落伍。

    • Thomas Kuhn 指出,任何成熟的科学,实际上是通过解决问题而发展起来的,而这些问题通常被看作本领域良好工作的例子,并且可用作将来进行工作的例子。

    • 学习别人的编程(代码)是有重要意义的。

  • 诚实

    • 不假装你是一个编程能手, 乐于承认自己的错误
  • 交流和合作

    • 开发程序首先应同程序员交流,其次则是和计算机交流。
  • 懒惰

  • 经验

    • 在许多其它领域中,基本知识变化缓慢,以致于 10 年前毕业的某人所学到的知识在现在仍没有什么变化。而在软件开发中,即使基本的知识也发展迅速,在你以后 10 年毕业的某个人可能学到了二倍于你的有效编程方法,一些老的程序员往往被另眼相看,不是由于他们对某些特定方法缺乏接触,而由于他们在走出校门后对一些闻名的基本编程概念缺乏了解。
    • 如果你不因时间而做出应变,你的经验与其说是帮助倒不如说是一个阻碍。
  • 习惯

    • 微软公司的 Bill Gates——曾说过,任何好程序员在开始的几年都做得很好。从那以后,程序员的好坏便基本定型了。
    • 成为某方面好的或差的程序员,主要是靠你自己的所作所为。建筑师要通过建筑而程序员要通过编程。你所作所为习惯,决定了你的编程品行。最终,你的习惯好坏决定了你是否能成为一位好的程序员。
    • 当你开始学习某一件事时,你应按正确的方式学好它。
    • 当你开始学时,你已对其进行了思考,并且你可在正确或错误的途径间作出轻易的选择。在你作过一段时间后,你对你所作的不太注意,此时“习惯的力量”会开始起作用。确保起作用的习惯是你所希望的。

小结:

  • 你的个人性格直接影响你编写计算机程序的能力。
  • 最有明显作用的性格为:谦虚、好奇心、诚实、创造性和纪律,还有文明的“懒惰”。
  • 高级程序员的发展和生成与天才并无多大联系,任何事情都和个人的发展有关。
  • 令人吃惊的是,小聪明、经验、坚持和欲望既可帮助你也能妨碍你。
  • 许多程序员不主动去吸收新信息和新技术,而是靠偶然地上获得一些新信息,如果你抽出少量时间学习别人的编程经验,过一段时间后,你将在你的同行中脱颖而出。
  • 好的性格对养成良好习惯有很大影响。为了成为一位高水平的程序员,你应养成良好
    的习惯,其余的就会随之而来。

1_move基础

总结

move的语法和rust差不多,核心概念简单,上手快。

  • 语法: move语法基本上和Rust语法大同小异, 以下为几个不同之处

    • module
    • script
    • 函数声明 fun
    • 函数访问性: public
    • 结构体特性(ability): has
  • 核心概念: 常用的核心概念,整理如下

核心概念

  • Module(模块):

    • 包含结构体 和 更新结构体的函数
    • 发布(publish) 在地址上, 以供被调用, 例如: 0xCAFE就是模块发布的地址
      • 值需要发布(publish)才能存储在global storage, 即调用 move_to进行发布
    1
    2
    3
    4
    // sources/FirstModule.move
    module 0xCAFE::BasicCoin {
    ...
    }
  • Script(脚本):

    • 入口函数
    • 执行程序
    • 临时的
    • 不存储在 global storage中
  • Struct(结构体): 结构体

  • Resource(资源):

    • 结构体中的 不可拷贝不可销毁(drop) 的值(values)
    • 必须在函数尾部(end)转移资源的权限
  • Address(地址): 地址

  • Global Storage(全局存储): 全局数据库

    • 类似下面的结构:
      1
      2
      3
      4
      5
      6
      7
      struct GlobalStorage {
      // 资源(values)
      resources: Map<address, Map<ResourceType, ResourceValue>>

      // 模块(code)
      modules: Map<address, Map<ModuleName, ModuleBytecode>>
      }

Move区块链的状态(state)模型

与EVM链的状态模式对比

Struct的4大特性(ability)

例如:

1
2
3
struct Coin has key {
value: u64,
}

Global Storage的5个操作

https://move-language.github.io/move/global-storage-operators.html

  • move_to<T>(&signer,T): 将T发布在 signer.address地址下
  • move_from<T>(address): T: 将地址下的T移除
  • borrow_global_mut<T>(address): &mut T: 获取地址下的可变引用
  • borrow_global<T>(address): &T: 获取不可变引用
  • exists<T>(address): bool: 判断地址下是否存在T

Acquires 修饰

当一个函数通过move_from, borrow_global, 或 borrow_global_mut来使用资源(resource)时,函数需要加上 acquires修饰

例如:

1
2
3
4
5
6
7
/// Deposit `amount` number of tokens to the balance under `addr`.
fun deposit(addr: address, check: Coin) acquires Balance{
let balance = balance_of(addr);
let balance_ref = &mut borrow_global_mut<Balance>(addr).coin.value;
let Coin { value } = check;
*balance_ref = balance + value;
}

函数

public, public(friend), or public(script)

  • 函数默认是私有的(private), 可以通过下面几种方式进行限定
    • public:
      • 同一模块的函数可调用
      • 其他模块中的函数可调用
      • 脚本中的函数可调用
    • public(friend) : 供 friend 模块调用(类似C++中的友元函数)
      • 同一模块
      • 其他友元模块中的函数
    • public entry
      • 相当于一个模块的main函数(入口)
    • public(script) : 供交易脚本调用

单元测试

move中的单元测试 和 rust单元测试类似, 直接在模块中编写单元测试即可

move test : 运行单元测试

1
2
3
4
5
6
#[test(account = @0x1)]  // 将 account 参数设置为地址 0x1
#[expected_failure(abort_code = 2)] // 预期失败,错误码 2
fun publish_balance_already_exists(account: signer) {
publish_balance(&account);
publish_balance(&account);
}

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module 0xCAFE::BasicCoin {
// Only included in compilation for testing. Similar to #[cfg(testing)]
// in Rust. Imports the `Signer` module from the MoveStdlib package.
#[test_only]
use std::signer;

struct Coin has key {
value: u64,
}

public fun mint(account: signer, value: u64) {
move_to(&account, Coin { value })
}

// 单元测试
// Declare a unit test. It takes a signer called `account` with an
// address value of `0xC0FFEE`.
#[test(account = @0xC0FFEE)]
fun test_mint_10(account: signer) acquires Coin {
let addr = signer::address_of(&account);
mint(account, 10);
// Make sure there is a `Coin` resource under `addr` with a value of `10`.
// We can access this resource and its value since we are in the
// same module that defined the `Coin` resource.
assert!(borrow_global<Coin>(addr).value == 10, 0); // assert 可以指定错误码
}
}

phantom类型

为什么需要phantom类型?

  • 编译时类型约束:Phantom 类型允许你在泛型参数中提供类型信息,而不需要实际在运行时使用这些类型。这使得编译器能够在编译时检查类型安全性,而不引入运行时开销。
  • 零运行时开销:因为 phantom 类型在运行时并不占用任何存储空间,它避免了不必要的内存分配或额外的存储复杂性。
  • 增强的泛型能力:在某些场景下,你可能希望在泛型中传递一些类型信息,但这些信息仅在编译时有用。在 Move 中使用 phantom 类型可以让代码更加灵活。

phantom常见应用场景:

  • 逻辑分离:通过 phantom 类型标记不同的逻辑上下文,确保类型安全。
  • 权限控制:在编译时限制某些操作只能由特定权限的账户执行,确保系统安全。
  • 标记类型:用 phantom 来标记不同状态或角色,避免运行时状态混乱。
  • 资源类型的泛化:对资源类型进行泛化处理,使得代码在不同资源类型上可重用,减少代码重复。

示例: 逻辑分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module PhantomLogicExample {
/// 定义两种逻辑上下文的类型标记
struct Deposit {}
struct Withdraw {}

/// 泛型结构体,用于表示不同逻辑的账户操作,但通过 phantom 类型来区分逻辑
struct Account<T: store, phantom Operation> {
balance: u64,
operation_info: T,
}

/// 创建一个用于“存款”逻辑的账户结构
public fun create_deposit_account<T: store>(balance: u64, info: T): Account<T, Deposit> {
Account { balance, operation_info: info }
}

/// 创建一个用于“取款”逻辑的账户结构
public fun create_withdraw_account<T: store>(balance: u64, info: T): Account<T, Withdraw> {
Account { balance, operation_info: info }
}

/// 增加存款逻辑的账户余额
public fun add_deposit<T: store>(account: &mut Account<T, Deposit>, amount: u64) {
account.balance = account.balance + amount;
}

/// 减少取款逻辑的账户余额
public fun subtract_withdraw<T: store>(account: &mut Account<T, Withdraw>, amount: u64) {
account.balance = account.balance - amount;
}

/// 获取账户余额 (适用于任意逻辑)
public fun get_balance<T: store, Operation>(account: &Account<T, Operation>): u64 {
account.balance
}
}

示例: 资源类型的泛化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

module ResourceExample {
struct Coin<phantom Currency> {
amount: u64,
}

struct USDCoin {}
struct Bitcoin {}

public fun create_usd_coin(amount: u64): Coin<USDCoin> {
Coin { amount }
}

public fun create_bitcoin(amount: u64): Coin<Bitcoin> {
Coin { amount }
}

/// 转移代币
public fun transfer<T>(coin: &mut Coin<T>, amount: u64) {
coin.amount = coin.amount - amount;
}

/// 获取代币余额
public fun get_balance<T>(coin: &Coin<T>): u64 {
coin.amount
}
}

示例: 类型标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module StateExample {
struct Pending {}
struct Approved {}
struct Rejected {}

struct Transaction<phantom Status> {
id: u64,
amount: u64,
}

public fun create_pending_transaction(id: u64, amount: u64): Transaction<Pending> {
Transaction { id, amount }
}

public fun approve_transaction(pending_txn: Transaction<Pending>): Transaction<Approved> {
Transaction { id: pending_txn.id, amount: pending_txn.amount }
}

public fun reject_transaction(pending_txn: Transaction<Pending>): Transaction<Rejected> {
Transaction { id: pending_txn.id, amount: pending_txn.amount }
}

public fun process_approved_transaction(approved_txn: Transaction<Approved>) {
// 处理已批准的交易
}
}

形式化验证

https://github.com/move-language/move/blob/main/language/move-prover/doc/user/spec-lang.md

move prove

1
2
3
4
5
6
# 在move的根目录下执行
./scripts/dev_setup.sh -yp
source ~/.profile

# 检查 boogie是否按照
boogie /version

形式化验证用于验证逻辑是否

常用的关键词:

  • let:
  • aborts_if: 条件中断
  • let post : 函数执行后获取值
  • ensures: 函数执行成功后必须满足的条件
  • ensures result: result是函数的返回值, ensures result即判断函数执行后的返回值

关于move spec形式化验证 与 单元测试的区别:

  • ChatGPT的回答:https://chatgpt.com/share/67079e85-c11c-8004-9d5d-52f8d4c16583
  • Move Spec 和单元测试是互补的。Move Spec 可以提供形式化的逻辑保障,而单元测试则用来确保代码在实际运行环境中表现正确。
  • 重点放在关键性质上:形式化验证特别适合那些涉及安全性、资金管理、状态变化一致性等至关重要的合约部分,而单元测试则适合验证合约中常见的逻辑操作和具体功能。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spec withdraw {
let balance = global<Balance<CoinType>>(addr).coin.value;
// 判断资源是否存在
aborts_if !exists<Balance<CoinType>>(addr);
// 校验balance
aborts_if balance < amount;

// 检查执行后的状态
let post balance_post = global<Balance<CoinType>>(addr).coin.value;
// 余额检查
ensures balance_post == balance - amount;
// 检查返回值
ensures result == Coin<CoinType> { value: amount };
}

特别注意: 在 Move 的形式化验证中,每一个可能的 abort(异常中断)都需要在 spec 规范中通过 aborts_if 子句来描述。当你编写了涉及可能导致 abort 的操作时,Move Prover 需要你为这些操作提供明确的终止条件。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun deposit<CoinType>(addr: address, check: Coin<CoinType>) acquires Balance{
let balance = balance_of<CoinType>(addr);
let balance_ref = &mut borrow_global_mut<Balance<CoinType>>(addr).coin.value;
let Coin { value } = check;
*balance_ref = balance + value;
}

spec deposit {
let balance = global<Balance<CoinType>>(addr).coin.value;
let check_value = check.value;

// 对应 borrow_global<Balance<CoinType>>(owner).coin.value
aborts_if !exists<Balance<CoinType>>(addr);

// 对应 *balance_ref = balance + value;
aborts_if balance + check_value > MAX_U64;

let post balance_post = global<Balance<CoinType>>(addr).coin.value;
ensures balance_post == balance + check_value;
}

其中2个 aborts_if 缺一不可,必须都存在

可以通过 move provier 的提示来增加

Rust/Go/Python调用C++

关于 extern "C"的作用

C++导出函数给其他语言使用必须使用C语言格式的函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#ifndef __XXXX_H
#define __XXXX_H


#ifdef __cplusplus
extern "C" {
#endif

// 需要导出的C++函数
// ...

#ifdef __cplusplus
}
#endif


#endif // __XXXX_H

如何解决 C++中 class 的导出问题

因为 C++的函数只能以 C 语言的格式导出,即使用extern "C" , 那么如何处理 类方法的导出?

解决方案: 增加一个中间层(抽象层)

例如, C++的class MyData实现了细节, 那么可以增加一个 C 语言风格的结构体指针 struct Data,利用 Data 将 MyData 的方法实现封装(wrapper)

示例代码:

mydata.h:

1
2
3
4
class MyData {
//...
void doSomething();
}

mydata.cc

1
2
3
4
5
6
#include "mydata.h"
void MyData::doSomething()
{
// doSomething
}

封装抽象层: data.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#ifndef __DATA_H
#define __DATA_H

#ifdef __cplusplus
extern "C" {
#endif
typedef struct Data Data;
// 导出的函数
void doSomething(Data *p);
#ifdef __cplusplus
}
#endif

#endif // __DATA_H

data.cc 对调用不可见

1
2
3
4
5
6
7
8
void doSomething(Data *p)
{
//...

// 指针类型转换, 对调用不可见
((MyData *)p)->doSomething();
}

示意图:

graph LR

Rust[Rust调用]--data.h-->data.cc[抽象层: data.cc]-.->mydata[实现层: mydata.h/mydata.cc]

Go[Go调用]--data.h-->data.cc

Python[Python调用] --data.h-->data.cc

详细代码:

https://github.com/youngqqcn/call-cpp-dylib/

  • Copyrights © 2021-2024 youngqqcn

请我喝杯咖啡吧~