{clean code}
Volodymyr Kupriienko
Our plan 🎯
What is clean code?
1.
# Intro
Analyze examples
2.
Define heuristic rules
3.
Multithreading
3.
Architecture
4.
Code Examples
Special for the Front-end team 🙌🏻
# Intro
# Intro
Clean code is
Software development practice described in book written by Robert C. Martin (uncle Bob)
Чистий код можуть читати і удосконалювати інші розробники, крім його вихідного автора.
Чистий код - це код, над яким ретельно попрацювали. Хтось не пошкодував часу, щоб зробити його простим і чітким. Хтось приділив належну увагу всім дрібницям і поставився до коду з душею.
What does this code do? 🧐
func main() {
c := &User{Name: "Kate", Ch: make(chan string)}
arrEmployees := []*Employee{
{Name: "John", Type: 1},
{Name: "Liza", Type: 2},
{Name: "Garry", Type: 2},
}
go func() {
for _, e := range arrEmployees {
if e.Type == 2 {
c.Ch <- fmt.Sprintf("Hello, I'm %s", e.Name)
time.Sleep(time.Second)
c.Ch <- fmt.Sprintf("Waiting time is 5m")
break
}
}
close(c.Ch)
}()
for m := range c.Ch {
fmt.Println(m)
}
}
type User = {...}
type Employee = {...}
const c: User = {
name: "Kate",
ch: (m: string) => console.log(m),
};
const arrEmployees: Employee[] = [
{name: "John", type: 1},
{name: "Liza", type: 2},
{name: "Garry", type: 2},
]
for (let e of arrEmployees) {
if (e.type == 2) {
c.ch(`Hello, I'm ${e.name}`)
setTimeout(
() => c.ch("Waiting time is 5m"),
1000
)
break
}
}
# Intro
1
Naming
Let's figure it out by cleanup 🧹
- Short and descriptive
- Ubiquitous language
- Context
Naming
# Naming
Let's fix naming 🪄🎩
-type User struct {
- Name string
- Ch chan string
}
+type Passenger struct {
+ FirstName string
+ NotificationChannel chan string
}
-type Employee struct {
- Name string
- Type int
}
+type DriverRank int
+
+const (
+ DriverRankStandard DriverRank = 1
+ DriverRankPremium DriverRank = 2
+)
+
+type Driver struct {
+ FirstName string
+ Rank DriverRank
}
-c := &User{Name: "Kate", Ch: make(chan string)}
+passenger := &Passenger{
+ FirstName: "Kate",
+ NotificationChannel: make(chan string)
+}
-arrEmployees := []*Employee{
- {Name: "John", Type: 1},
- {Name: "Liza", Type: 2},
- {Name: "Garry", Type: 2},
}
+availableDrivers := []*Driver{
+ {FirstName: "John", Rank: DriverRankStandard},
+ {FirstName: "Liza", Rank: DriverRankPremium},
+ {FirstName: "Garry", Rank: DriverRankPremium},
}
-type User = {
- name: string
- ch: (m: string) => void
}
+type channel = (update: string) => void
+
+type Passenger = {
+ firstName: string
+ notificationChannel: channel
}
-type Employee = {
- name: string
- type: number
}
+enum Rank {
+ STANDARD = 1,
+ PREMIUM = 2,
+}
+
+type Driver = {
+ firstName: string
+ rank: Rank
}
-const c: User = {
- name: "Kate",
- ch: (m: string) => console.log(m),
};
+const passengerKate: Passenger = {
+ firstName: "Kate",
+ notificationChannel: (update: string) => console.log(update),
};
-const arrEmployees: Employee[] = [
- {name: "John", type: 1},
- {name: "Liza", type: 2},
- {name: "Garry", type: 2},
]
+const availableDrivers: Driver[] = [
+ {firstName: "John", rank: Rank.STANDARD},
+ {firstName: "Liza", rank: Rank.PREMIUM},
+ {firstName: "Garry", rank: Rank.PREMIUM},
]
# Naming
Let's fix naming 🪄🕊
go func() {
- for _, e := range arrEmployees {
- if e.Type == 2 {
- c.Ch <- fmt.Sprintf("Hello, I'm %s", e.Name)
time.Sleep(time.Second)
- c.Ch <- fmt.Sprintf("Waiting time is 5m")
break
}
}
- close(c.Ch)
+ for _, driver := range availableDrivers {
+ if driver.Rank == DriverRankPremium {
+ passenger.NotificationChannel
+ <- fmt.Sprintf("Hello, I'm %s", driver.Name)
time.Sleep(time.Second)
+ passenger.NotificationChannel
+ <- fmt.Sprintf("Waiting time is 5m")
break
}
}
+ close(passenger.NotificationChannel)
}()
-for m := range c.Ch {
- fmt.Println(m)
}
+for update := range passenger.NotificationChannel {
+ fmt.Println(update)
}
-for (let e of arrEmployees) {
- if (e.type == 2) {
- c.ch(`Hello, I'm ${e.name}`)
setTimeout(
- () => c.ch("Waiting time is 5m"),
1000
)
break
}
}
+for (let driver of availableDrivers) {
+ if (driver.rank == Rank.PREMIUM) {
+ passenger.notificationChannel(
+ `Hello, I'm ${driver.name}`
+ )
setTimeout(
+ () => passenger.notificationChannel(
+ "Waiting time is 5m"
+ ),
1000
)
break
}
}
# Naming
1
Naming ✅
2
Methods
What next? 🤩
# Methods
Methods
- Short
- Single responsibility
- No side effects
- Param objects & no flags
- Don't repeat yourself
Let's split it by methods 🖖🏻
# Methods
+func callTaxi(passenger *Passenger) {
for _, driver := range availableDrivers {
if driver.Rank == DriverRankPremium {
passenger.NotificationChannel <- fmt.Sprintf(
"Hello, I'm %s",
driver.Name
)
time.Sleep(time.Second)
passenger.NotificationChannel
<- fmt.Sprintf("Waiting time is 5m")
break
}
}
close(passenger.NotificationChannel)
+}
+func notifyPassengerOnUpdates(passenger *Passenger) {
for update := range passenger.NotificationChannel {
fmt.Println(update)
}
+}
+const callTaxi = (passenger: Passenger) => {
for (let driver of this.availableDrivers) {
if (driver.rank == Rank.PREMIUM) {
passenger.notificationChannel(
`Hello, I'm ${driver.name}`
)
setTimeout(
() => passenger.notificationChannel(
"Waiting time is 5m"
),
1000
)
break
}
}
+}
Let's split it by methods ✋🏻
# Methods
func callTaxi(customer *Customer) {
for _, driver := range availableDrivers {
if driver.Rank == DriverRankPremium {
passenger.NotificationChannel <- fmt.Sprintf(
"Hello, I'm %s",
driver.Name
)
time.Sleep(time.Second)
passenger.NotificationChannel
<- fmt.Sprintf("Waiting time is 5m")
break
}
}
close(passenger.NotificationChannel)
}
func notifyPassengerOnUpdates(passenger *Passenger) {
for update := range passenger.NotificationChannel {
fmt.Println(update)
}
}
const callTaxi = (passenger: Passenger) => {
for (let driver of this.availableDrivers) {
if (driver.rank == Rank.PREMIUM) {
passenger.notificationChannel(
`Hello, I'm ${driver.name}`
)
setTimeout(
() => passenger.notificationChannel(
"Waiting time is 5m"
),
1000
)
break
}
}
}
Ящо це метод, завжди застосовую до нього прийом “вилучення методу”; у результаті в мене залишається основний метод, який більш чітко пояснює, що саме він робить, і декілька підметодів, що пояснюють, як він це робить.
Let's split it by methods 🖖🏻
# Methods
func callTaxi(passenger *Passenger) {
- for _, driver := range availableDrivers {
- if driver.Rank == DriverRankPremium {
+ driver := findDriver(DriverRankPremium)
passenger.NotificationChannel <- fmt.Sprintf(
"Hello, I'm %s",
driver.Name
)
time.Sleep(time.Second)
passenger.NotificationChannel
<- fmt.Sprintf("Waiting time is 5m")
- break
- }
- }
close(passenger.NotificationChannel)
}
+func findDriver(rank DriverRank) *Driver {
+ availableDrivers := []*Driver{
+ {FirstName: "John", Rank: DriverRankStandard},
+ {FirstName: "Liza", Rank: DriverRankPremium},
+ {FirstName: "Garry", Rank: DriverRankPremium},
+ }
+
+ for _, driver := range availableDrivers {
+ if driver.Rank == rank {
+ return driver
+ }
+ }
+
+ return nil
+}
func notifyPassengerOnUpdates(passenger *Passenger) {
for update := range passenger.NotificationChannel {
fmt.Println(update)
}
}
const callTaxi = (passenger: Passenger) => {
- for (let driver of this.availableDrivers) {
- if (driver.rank == Rank.PREMIUM) {
+ const driver = findDriver(Rank.PREMIUM)
passenger.notificationChannel(
`Hello, I'm ${driver.name}`
)
setTimeout(
() => passenger.notificationChannel(
"Waiting time is 5m"
),
1000
)
- break
- }
- }
}
+const findDriver = (rank: Rank): Driver => {
+ const availableDrivers: Driver[] = [
+ {firstName: "John", rank: Rank.STANDARD},
+ {firstName: "Liza", rank: Rank.PREMIUM},
+ {firstName: "Garry", rank: Rank.PREMIUM},
+ ]
+
+ return availableDrivers.find(
+ (driver: Driver) => driver.rank === rank
+ )
+}
3
Tests
2
Methods ✅
1
Naming ✅
OK, that's all? 🙉
- TDD
- Scenarios design
- Refactoring
- Good quality
Tests
# Tests
Код без тестів не можна вважати чистим, хоч бияким елегантним він не був і хоч би я добре читався.
Process
Write test scenarios 💭
1.
# Tests
Write code 👨🏻💻
2.
Run tests 🚀
3.
Fix issues 🪲
3.
Refactor code 🧹
4.
Repeat 😉
5.
Закон Леблана: потім означає ніколи
1
Naming ✅
2
Methods ✅
3
Tests ✅
Formatting
- Reduces MR size
- Reduces conflicts
- Can be automated
- Code should be like article
5
Comments
4
Formatting ✅
1
Naming ✅
2
Methods ✅
3
Tests ✅
Code must be self-documented
# Comments
Avoid muttering
# Comments
Avoid commented code
# Comments
Good comments
- Use var/methods instead
- Describe intentions
- TODO comments
# Comments
5
Comments ✅
4
Formatting ✅
6
Objects & error handling
1
Naming ✅
2
Methods ✅
3
Tests ✅
Objects & Structs
- Name by state, not behavior
- DTO
- Law of Demeter
Error handling
- Add context
- Don't return error codes
- Don't return bool or null
- Separate domain logic and error handling
The Boy Scout Rule
1
Naming matters
2
We are reading code mostly
3
We work in a team
5
Improves stability and reliability
4
Boosts development process
6
All loves good quality code
Why it's important? 🤔
Book
💙💛
Ukrainian
Original
And remember
👇🏻
That's all✨
Clean Code
By Volodymyr Kupriienko
Clean Code
- 203