CRITICAL fixes: - fileio.go: Add sync.Mutex to file handle table (race condition #2) allocHandle/getHandle/removeHandle thread-safe helpers - goroutine.go: Add defer/recover to GoLaunch/GoLaunchBlock Goroutine panic no longer crashes entire process (#5) HIGH fixes: - strings.go: Implement proper LTrim (TrimLeft) and RTrim (TrimRight) Previously both aliased to AllTrim — silent semantic bug (#18) - register.go: TRIM = RTrim (Harbour compatible) From 53-issue senior code review. Remaining: 47 issues (HIGH: 10, MEDIUM: 18, LOW: 16, CRITICAL: 3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
252 lines
6.3 KiB
Markdown
252 lines
6.3 KiB
Markdown
# Five's Readability — The Real Advantage in the AI Era
|
|
|
|
## Why Readability Matters When AI Writes Code
|
|
|
|
AI generates code automatically now.
|
|
But there's a problem:
|
|
|
|
1. **AI creates complex code** — it works, but humans can't understand it
|
|
2. **Unmaintainable without AI** — if the AI service goes down, code becomes a black box
|
|
3. **Bugs hide in complexity** — if you can't read it, you can't debug it
|
|
4. **Team collaboration breaks** — nobody except the AI understands the code
|
|
|
|
**Five solves this fundamentally.**
|
|
|
|
## Same Task, Different Readability
|
|
|
|
### Query Customer List
|
|
|
|
```prg
|
|
// Five (PRG) — 8 lines
|
|
USE customers NEW
|
|
SET FILTER TO balance > 10000
|
|
GO TOP
|
|
DO WHILE !Eof()
|
|
? name, city, balance
|
|
SKIP
|
|
ENDDO
|
|
```
|
|
|
|
```go
|
|
// Go — 27 lines
|
|
func listCustomers() {
|
|
db, err := sql.Open("sqlite3", "customers.db")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
rows, err := db.Query(
|
|
"SELECT name, city, balance FROM customers WHERE balance > $1",
|
|
10000,
|
|
)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var name, city string
|
|
var balance float64
|
|
if err := rows.Scan(&name, &city, &balance); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println(name, city, balance)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
```
|
|
|
|
| Language | Lines | Error handling | Non-developer readable |
|
|
|----------|------|---------------|:---:|
|
|
| **Five (PRG)** | **8** | Built-in | **Yes** |
|
|
| Python | 12 | Manual | Maybe |
|
|
| Go | 27 | Required | No |
|
|
| Java | 15 | Required | No |
|
|
|
|
## Why PRG is Easy to Read
|
|
|
|
### 1. Reads like English sentences
|
|
|
|
```prg
|
|
USE customers NEW // Open customer file
|
|
GO TOP // Go to the first record
|
|
DO WHILE !Eof() // Repeat until end of file
|
|
IF balance > 10000 // If balance exceeds 10000
|
|
? name, balance // Print name and balance
|
|
ENDIF // End condition
|
|
SKIP // Move to next record
|
|
ENDDO // End loop
|
|
```
|
|
|
|
Even non-programmers can follow the flow.
|
|
No `{`, `}`, `func`, `defer`, or cryptic symbols.
|
|
|
|
### 2. Everything is explicit
|
|
|
|
```prg
|
|
// Five — endings are clear
|
|
IF condition
|
|
...
|
|
ENDIF // IF ends here
|
|
|
|
FOR i := 1 TO 10
|
|
...
|
|
NEXT // FOR ends here
|
|
|
|
DO WHILE condition
|
|
...
|
|
ENDDO // WHILE ends here
|
|
```
|
|
|
|
```go
|
|
// Go — count the braces
|
|
if condition {
|
|
for i := 0; i < 10; i++ {
|
|
if another {
|
|
// ...
|
|
} // ← which one does this close?
|
|
} // ← this one?
|
|
} // ← or this one?
|
|
```
|
|
|
|
### 3. Variable names tell their type
|
|
|
|
```prg
|
|
LOCAL cCustomerName, nTotalBalance, dLastPurchase, lIsActive
|
|
|
|
cCustomerName := "Charles KWON" // c = Character
|
|
nTotalBalance := 15000.50 // n = Numeric
|
|
dLastPurchase := Date() // d = Date
|
|
lIsActive := .T. // l = Logical
|
|
```
|
|
|
|
Hungarian notation makes types visible in names:
|
|
- `c` = Character (string)
|
|
- `n` = Numeric (number)
|
|
- `d` = Date
|
|
- `l` = Logical (boolean)
|
|
- `a` = Array
|
|
- `o` = Object
|
|
|
|
Even AI-generated code is self-documenting with these prefixes.
|
|
|
|
### 4. DEFER keeps it safe AND clean
|
|
|
|
```go
|
|
// Go — defer exists but err checks pollute the code
|
|
db, err := sql.Open("sqlite3", dsn)
|
|
if err != nil {
|
|
return fmt.Errorf("open failed: %w", err)
|
|
}
|
|
defer db.Close()
|
|
result, err := db.Query("SELECT ...")
|
|
if err != nil {
|
|
return fmt.Errorf("query failed: %w", err)
|
|
}
|
|
defer result.Close()
|
|
```
|
|
|
|
```prg
|
|
// Five — DEFER + clean business logic
|
|
db := sql.Open("sqlite", dsn)
|
|
DEFER db:Close() // Same as Go's defer!
|
|
|
|
result := SqlScan(db, "SELECT ...")
|
|
// Errors handled in one place via BEGIN SEQUENCE
|
|
// But the code stays readable
|
|
```
|
|
|
|
Five brings Go's `defer` as `DEFER` — same safety, same guarantee.
|
|
The good parts of Go, without the `if err != nil` noise.
|
|
|
|
## AI-Era Scenarios
|
|
|
|
### Scenario 1: AI service goes down
|
|
|
|
**Go/Python/Java project:**
|
|
- 100K lines — days to understand structure
|
|
- Complex type systems, generics, interfaces
|
|
- Framework dependencies to untangle
|
|
- New developer needs weeks to onboard
|
|
|
|
**Five project:**
|
|
- Same features in 30K lines — less code to read
|
|
- `USE`, `SKIP`, `SEEK` — commands mean what they say
|
|
- Inexperienced developer can read code in one day
|
|
|
|
### Scenario 2: Emergency bug fix
|
|
|
|
```prg
|
|
// Assume the bug is here
|
|
USE orders NEW
|
|
SET FILTER TO date >= Date() - 30
|
|
GO TOP
|
|
DO WHILE !Eof()
|
|
IF amount > 0
|
|
nTotal += amount // ← bug here: should be = instead of +=?
|
|
ENDIF
|
|
SKIP
|
|
ENDDO
|
|
```
|
|
|
|
Non-developers (sales team, managers) can read this and understand
|
|
"nTotal is accumulating something." This level of comprehension is
|
|
impossible with Go code.
|
|
|
|
### Scenario 3: Verifying AI-generated code
|
|
|
|
```prg
|
|
// AI-generated Five code — humans can verify
|
|
FUNCTION CalcDiscount(nPrice, nQuantity)
|
|
LOCAL nDiscount
|
|
IF nQuantity >= 100
|
|
nDiscount := nPrice * 0.15 // 100+ units: 15% discount
|
|
ELSEIF nQuantity >= 50
|
|
nDiscount := nPrice * 0.10 // 50+ units: 10% discount
|
|
ELSE
|
|
nDiscount := 0 // No discount
|
|
ENDIF
|
|
RETURN nDiscount
|
|
```
|
|
|
|
A business stakeholder can look at this code and ask:
|
|
"Is 15% for 100+ units correct?" — try doing that with Go generics.
|
|
|
|
## Five + Go = The Optimal Combination
|
|
|
|
Simplicity alone isn't enough.
|
|
**Simple code with powerful capabilities** — that's the goal.
|
|
|
|
```prg
|
|
IMPORT "database/sql"
|
|
IMPORT _ "modernc.org/sqlite"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL db, aRows, i
|
|
|
|
db := sql.Open("sqlite", ":memory:")
|
|
DEFER db:Close()
|
|
|
|
db:Exec("CREATE TABLE orders (id INTEGER, amount REAL)")
|
|
db:Exec("INSERT INTO orders VALUES (1, 15000)")
|
|
|
|
aRows := SqlScan(db, "SELECT * FROM orders")
|
|
FOR i := 1 TO Len(aRows)
|
|
? aRows[i]["id"], aRows[i]["amount"]
|
|
NEXT
|
|
|
|
RETURN
|
|
```
|
|
|
|
SQL database access in PRG syntax.
|
|
Go's `database/sql` powers it behind the scenes,
|
|
but the person reading this code **doesn't need to know Go**.
|
|
|
|
## The Bottom Line
|
|
|
|
> **In an era where AI writes code,
|
|
> code that humans can read is the real asset.
|
|
> Five is the only systems language where AI-generated code
|
|
> can be verified by humans — even non-developers.**
|