電腦一開機,會從 chip load BIOS 到 memory 中,並且 initialize 它,這時候我們能用的功能只有 BIOS。

因為這時候還沒有 file system 的概念,BIOS 無法像 load file 一樣從一個 file load OS,只能讀取特定 sector 的 data。通常一個 sector 的 size 是 512 byte,並且以 cylinder I、head J、Sector K 表示其位置。

第一個 sector(clyiner 0、head 0、sector 0)稱為 boot sector。BIOS 由第一個 sector 的最後兩個 byte 是不是 0xaa55 來判斷這個 sector 是不是 bootable。

所以說,最簡單的 boot sector 長這樣:

1
2
3
4
e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
... (many zero)
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa

最前面的三個 byte 0xe9 0xfd 0xff 是一個 infinite loop 的 machine instruction,會一直 jump 到同一個位置。

最後兩個 byte 是 0x550xaa。這裡之所以不是像前面寫的 0xaa55 是因為 x86 架構是以 little endian 來表示數值,也就是低位在前、高位在後(跟我們平常習慣高位在前相反),所以才會是先 0x550xaa

最經典的例子就是 printf()

1
int printf(char* fmt, ...)

後面的 ... 表示 variable length argument list,必須放在最後面。function 要用 variable length argument list 至少要有一個有名稱的參數,而且可以從這個參數中得知 variable length argument list 總共有多少參數。

在 function 裡處理 variable length argument list 主要要做的是從沒有對應名稱的 list 中取得內容,也就是要拿到參數們。標準函式庫的 stdarg.h 提供一組 macro 來做這件事,Ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <stdio.h>
#include <stdarg.h>

void myfunc(char* fmt, ...)
{
va_list ap;
char* p = NULL;
va_start(ap, fmt);
for (p = fmt; *p; p++)
{
if (*p != '%')
{
putchar(*p);
continue;
}

++p; // %
switch (*p)
{
case 'd':
{
int val = va_arg(ap, int);
printf("%d", val);
break;
}
case 'f':
{
double val = va_arg(ap, double);
printf("%f", val);
break;
}
case 's':
{
for (char* val = va_arg(ap, char*); val && *val; ++val)
{
putchar(*val);
}
break;
}
default:
putchar(*p);
break;
}
}
va_end(ap);
}

int main()
{
int n = 123;
double f = 330.732;
char str[] = "KEKEKE";
myfunc("haha, test format: %d, %f, %s\n", n, f, str);
return 0;
}

至於 stdarg.h 裡怎麼定義 macro 的呢?

在 Ubuntu 底下追到 /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h 裡的:

1
2
3
#define va_start(v,l)   __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)

之後就不知道去哪了,似乎要到 GCC 的 source code 裡去看跟 builtin 有關的東西才找得到了。Ref

用測試執行時間可以很容易區分出哪些是 unit test 哪些是 integration test,速度快是 unit test。除此之外,也可以依照測試執行時是否 depend on external resource 來區分,例如 db、cache、third party library or API。

區分出 unit test 跟 integration test 後,把它們分開放。讓 unit test 形成一個綠色安全區域──如果這裡的 test failed,就是 code 有問題而非環境或者設定等等造成的 false alarm。

developer 應該要對綠色安全區域有信任感,建立信任感的方式是「fail 就是真的 code 有問題而非 false alarm」。一旦開發人員對 test 失去信任,可能變得不想執行或者 fail 時認為「fail 是正常的」而忽略它。

integration test 除了執行比較慢之外,還可能受到 external dependency 影響而結果不穩定,所以在面對 integration test 的時候,就要考慮有可能是 external dependency 造成 fail。

在自動化流程裡,unit test 應該要每次 check in code 都執行,integration test 則是至少在 daily build 要執行。

promptui 的使用

1
$ go get -t github.com/manifoldco/promptui

Import

1
import "github.com/manifoldco/promptui"

Input

1
2
prompt := promptui.Prompt{Label: "Project Name"}
prjName, err := prompt.Run()

執行會得到:

Select

1
2
3
4
5
prompt := promptui.Select{
Label: "Project Type",
Items: []string{"go", "quasar"},
}
_, prjType, err := prompt.Run()

執行會得到:

Facades

Laravel 提供的 Facade 是 Facade pattern。實作上利用 static function 把 Laravel 的功能包起來,可以在使用的時候更簡潔。Laravel 在 Facade 做些 function 讓測試時可以假造 Facade class 的結果,因此雖然是 static function 卻仍擁有可測試性。

使用 Facade 要注意以下兩點:

  • class 的大小。
    • Facade 依然是 class 的 dependency,使用 Facade 不會讓 dependency 顯現在 constructor,就不容易看得出 class 長得太大。
    • 這跟設計有關,只要設計 ok,當然可以用 Facade
  • 做與 Laravel 互動的 third party package 時,不適合用 Facade,應該要走一般 DI。
    • 因為 third party package 本身沒有 Laravel framework,無法是用 Laravel Facade 提供的 test 能力。

Contracts

Laravel 的 Contracts 是一堆定義 framework 提供的 core service 的 interface。所有 Laravel 的 contract 有他們自己的 Github repo,寫 package 時可以只使用其中幾個 contract。

Contracts vs Facades

每個 facade 都有對應的 contract。contract 跟 facade 是可以達到同樣事情的不同寫法或做法。

如果喜歡在 constructor explicit 的寫出一個 class 的 dependency,就用 contract。

一般來說,偏好使用 contract 還是 facade 對 application 而言沒有太大差別。但是 package 用 contract 會比較好,因為對 package 來說 contract 比較好測。

何時使用 Contracts

用 facade 還是用 contract 基本上是偏好問題。

有設計好 class 的責任,用 facade 跟 contract 沒太大差別。

用 contracts 的原因:loose coupling 跟 simplicity

Loose Coupling

contracts package 沒有任何 implementation 也沒有 dependency,所以 depend on 它不會被任何 library 或 framework 綁住,反而可以利用定義好的 interface,底下實作可以任意抽換。

Simplicity

定義良好的 interface 可以很容易看出 laravel 提供的功能。

比起依賴於複雜又龐大的 class,依賴於定義良好的 interface,code 比較好懂。

如何使用 Contracts

在 type hint 寫 contract 的名字,service container 會進行 resolve 並且 inject 適當的 instance。

Reference

定義:一個隔離框架是一套可用來幫助寫程式的 API,使用這套 API 來建立假物件比手刻假物件要容易得多、快得多、簡潔許多。它是一個可以在 runtime 建立和設定假物件的 library,這些假物件稱為 dynamic stub 或 dynamic mock。

之所以稱為隔離框架,是因為這些框架幫你隔離工作 unit 跟它的 dependency object。

使用隔離框架的好處是讓隔離框架幫我們生 stub 跟 mock object,省去手刻的麻煩。

動態產生假物件

動態假物件是在 runtime 才建立的 stub 或 mock object,它的建立不需要手刻假物件的 code(寫死在假 class 中)。

Codeception

可以用 Codeception Stub github 來製造假的 stub 跟 mock object。寫起來大概會長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
public function testFoo()
{
$foo = new Foo;
$this->assertEquals(1, $foo->bar());
$this->assertEquals(2, $foo->hasArg(1));
$this->assertEquals(5566, $foo->noFake());

$fooStub = Stub::make('Foo', ['bar' => 2, 'hasArg'> 3]);
$this->assertEquals(2, $fooStub->bar());
$this->assertEquals(3, $fooStub->hasArg(1));
$this->assertEquals(5566, $fooStub->noFake());
}
Foo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Foo {
public function bar() {
return 1;
}

public function hasArg($ha) {
return 2;
}

public function noFake()
{
return 5566;
}
}

除了 Stub::make() 之外還有 Stub::makeEmpty()。class name 可以是 interface。要小心不要把 Stub 當作 extract and override 在寫……

可以配合 Codeception\Stub\Expected 做 mock object。如果是 mock object,Stub::make() 系列要記得傳最後一個參數 testCase,不然像 Expected::once() 會沒 verify 到。

這可以省去寫一堆 ‘’class FakeXXX’’,但是針對 API json 或 xml response 還是只能手動把這些 json 跟 xml 存下來去做假物件。

SQLite 的 ORM package

https://pub.dev/packages/floor

看起來是走 Entity pattern 那一系的。覺得可以直接用 class 去定義 schema 好像不錯

Entity class

代表一筆資料的 class,也可以說是 table 的欄位。

1
2
3
4
5
6
7
8
9
10
11
12
13
// person.dart

import 'package:floor/floor.dart';

@entity
class Person {
@primaryKey
final int id;

String name;

Person(this.id, this.name);
}

DAO (Data Access Object)

負責 access 底層 SQLite database。得是 abstract class,method 必須 return FutureStream。並且用 annotation 標示 CRUD 的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// person_dao.dart

import 'package:floor/floor.dart';

import 'person.dart';

@dao
abstract class PersonDao {
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons();

@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);

@insert
Future<void> insertPerson(Person person);

@update
Future<void> updatePerson(Person person);

@delete
Future<void> deletePerson(Person person);
}

Database

要是一個 extend FloorDatabase 的 abstract class。

在 class signature 要加 @Database(),並且要把 entity 加進其中 entities attribute 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// database.dart

import 'dart:async';
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

import 'person_dao.dart';
import 'person.dart';

part 'database.g.dart'; // the generated code will be there

@Database(version: 1, entities: [Person])
abstract class AppDatabase extends FloorDatabase {
PersonDao get personDao;
}

import 後要記得加 part 'database.g.dart'

接著執行 flutter packages pub run build_runner buil 就會產生 database.g.dart。如果要在 database 改變的時候自動 build 出 database.g.dart,可以下 flutter packages pub run build_runner watch

最後是使用 database:

1
2
3
4
5
6
7
8
9
10
// database.db 是 db 的 file name
final database = await $FloorAppDatabase.databaseBuilder('database.db').build();
final personDao = database.personDao;

final person = Person(1, 'John');
await personDao.insertPerson(person);

final result = await personDao.findPersonById(1);

print(result.name);

Laravel 提供兩種主要的 authorizing action:gate 跟 policy

可以把 gates 跟 policies 想像成 routes 跟 controllers。

Gates are most applicable to actions which are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.

通常 gates 跟 policies 會混合使用。

Gates

Gates are simply closures that determine if a user is authorized to perform a given action.

gates 通常定義在 App\Providers\AuthServiceProvider::boot(),用 Gate facade 來定義:

1
2
3
4
5
6
7
8
public function boot()
{
$this->registerPolicies();

Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}

也可以用 class callback 形式:

1
Gate::define('update-post', [PostPolicy::class, 'update']);

定義 gate 後,要在需要擋權限的地方(例如 controller action)使用 Gate::allows()Gate::denies()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PostController extends Controller
{
/**
* Update the given post.
*/
public function update(Request $request, Post $post)
{
if (! Gate::allows('update-post', $post)) {
abort(403);
}

// Update the post...
}
}

不用自己丟目前登入的 user instance 進去,laravel 會 handle 這件事。

如果不是要看現在登入的 user,而是要 authorize 另外的 user,可以使用 forUser()

1
2
3
4
5
6
7
if (Gate::forUser($user)->allows('update-post', $post)) {
// The user can update the post...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
// The user can't update the post...
}

可以用 any()none() 來同時 authorize 多個 action:

1
2
3
4
5
6
7
if (Gate::any(['update-post', 'delete-post'], $post)) {
// The user can update or delete the post...
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
// The user can't update or delete the post...
}

如果想要在 authorize 失敗的時候自動丟 Illuminate\Auth\Access\AuthorizationException

1
Gate::authorize('update-post', $post);

Illuminate\Auth\Access\AuthorizationException 會自動被轉成 403 response by Laravel’s exception handler。

可以 support 複雜的 context:https://laravel.com/docs/8.x/authorization#gates-supplying-additional-context

Gate define 的時候也可以 return Illuminate\Auth\Access\Response 來帶更多資訊。Ref

如果想要讓某個 user 可以 grant all ability,可以用 before() Ref

1
2
3
4
5
Gate::before(function ($user, $ability) {
if ($user->isAdministrator()) {
return true;
}
});

If the before closure returns a non-null result that result will be considered the result of the authorization check.

也可以用 after 來定義在所有 authorization check 執行後執行的 closure。

Policies

Policies are classes that organize authorization logic around a particular model or resource.

可以用 make:policy 來產生 policy class,產生的 policy 會放在 app/Policies/

如果想要有跟某個 model 關聯的 example policy:

1
$ php artisan make:policy PostPolicy --model=Post

Register policy

有 policy class 後,要 register 它。

Registering policies is how we can inform Laravel which policy to use when authorizing actions against a given model type.

App\Providers\AuthServiceProvider 裡有個 policies property,它的 key 是 model class,value 是對應的 policy class name。

Registering a policy will instruct Laravel which policy to utilize when authorizing actions against a given Eloquent model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];

/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();

//
}
}

Write Policy

大致長這樣,可以寫任意 function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\Models\User $user
* @param \App\Models\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}

如果用 --model 生,會生出 CRUD 有關的 function。

使用 Policy

透過 User Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PostController extends Controller
{
/**
* Update the given post.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Post $post)
{
if ($request->user()->cannot('update', $post)) {
abort(403);
}

// Update the post...
}
}

沒有 model instance 的 policy:

1
2
3
4
5
6
7
8
public function store(Request $request)
{
if ($request->user()->cannot('create', Post::class)) {
abort(403);
}

// Create the post...
}

透過 Controller Helpers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PostController extends Controller
{
/**
* Update the given blog post.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);

// The current user can update the blog post...
}
}

如果是 resource controller,可以這樣:

1
2
3
4
5
6
7
8
9
10
11
12
class PostController extends Controller
{
/**
* Create the controller instance.
*
* @return void
*/
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
}

要注意 CRUD 的 function 的 signature 要符合 laravel 需要的 type hint 才能用

透過 middleware

1
2
3
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');

MySQL UUID Smackdown: UUID vs. INT for Primary Key

可以用 BINARY 存 UUID,然後用以下 function 做 human-readable format 的轉換:

  • UUID_TO_BIN
  • BIN_TO_UUID
  • IS_UUID

這些 function 要 MySQL 8.0 後才有。

Example

把 UUID 當 PK 的 example

create table

1
2
3
4
CREATE TABLE customers (
id BINARY(16) PRIMARY KEY,
name VARCHAR(255)
);

insertion

1
2
3
4
INSERT INTO customers(id, name)
VALUES(UUID_TO_BIN(UUID()),'John Doe'),
(UUID_TO_BIN(UUID()),'Will Smith'),
(UUID_TO_BIN(UUID()),'Mary Jane');

query

1
2
3
4
5
SELECT 
BIN_TO_UUID(id) id,
name
FROM
customers;