SoFunction
Updated on 2025-03-01

Go through invariance optimization program details

text

The concept of invariance is very simple, and after you create a structure, you can never modify it. This concept sounds pretty simple, but it is not that easy for your program to take advantage of it. Next, we use the concept of invariance in Go to make your code more readable and stable.

Reduce dependencies on global or external states

When we use the same parameters and execute the same function twice, we expect that we should get the same result. But when our function depends on external state or global variables, the function may output different results. We'd better avoid this.

The parameters of the function are always given, so when we call, we can always return the same function. If you have a shared global variable for something inside a function, consider passing that variable as a parameter instead of using it directly inside the function.

This can make your function's return value more predictable and easier to test, and the readability of the entire code will be improved, because the caller will know which values ​​will affect the behavior of the function. Wouldn't the role of parameters affect the return value?

Let's look at an example.

package main
import (
   "fmt"
   "math/rand"
   "time"
)
var randNum int
func main() {
   s1 := (().UnixNano())
   r1 := (s1)
   randNum = (100)
   (Add(1, 1))
}
func Add(a, b int) int {
   return a + b + randNum
}

AddGlobal variables are used in the functionrandNumAs part of the calculation, this is not reflected from the function signature. A better approach is global variablesrandNumShould be passed as a parameter as shown below.

func Add(a, b, randNum int) int {
   return a + b + randNum
}

This is more predictable, and if we need to modify the parameter, the scope of the impact is onlyAddin the function.

Export only functions of the structure, not member variables

We know,GoA member variable in a structure, if the first letter is capitalized, then the member variable is visible to the outside (this is determined by the compiler). Back to our blog, only struct functions are exported, not member variables, with the purpose of hoping that the data of member variables will be protected to ensure the valid state of member variables! Because this makes your code more reliable, you don't have to maintain every operation that modifys that member variable, because none of these operations will work.

Give an example

ackage main
import (
	"fmt"
)
type AK47 struct {
	bullet int
}
func NewAK47(bullet int) AK47 {
	return AK47{bullet: bullet}
}
func (a AK47) GetBullet() int {
	return 
}
func (a AK47) SetBullet(bullet int) {
	 = bullet
}
func main() {
	ak47 := NewAK47(30)
	(())
	(20)
	(())
}

We define a structureAK47, this gun has a member variablebulletThe number of bullets, it is a non-export field, we also define a constructorNewAK47And oneGetBulletfunction.

Once createdAK47, it cannot change its member variablebulletNow. You may be puzzled at this time, what if we need to modify member variables? Don't worry, you can try the following method.

Use copy values ​​in functions instead of pointers

In the previous subtitle, we mentioned a concept that you should never change the structure after you create it. However, in practice, we often need to modify member variables in structures.

While using invariance, we can still maintain multiple states of the instantiated structure, which does not mean that we break the structure and do not change it after it is created. What we change is its copy, that is, the copied structure. Copy structure? Do we need to implement many functions that copy each field of the structure?

Of course not, we can useGoThe characteristic of , when calling a function, the entry parameter is the behavior of copying the value. For operations that need to modify member variables in a structure, we can create a function that receives the structure as a parameter and returns a modified structure copy.

We can modify anything in that copy without changing the caller structure, which means there are no side effects on the original structure and the value of the structure is still predictable.

I don't know if you've used itGoStandard librarySliceSlice, in whichappendThe function uses this method. Let's continue to useAK47To implement this method

The code is as follows

package main
import (
	"fmt"
)
type AK47 struct {
	bullet int
}
func NewAK47(bullet int) AK47 {
	return AK47{bullet: bullet}
}
func (a AK47) GetBullet() int {
	return 
}
func (a AK47) AddBullet(ak47 AK47) AK47 {
	newAK47 := NewAK47(() + ())
	return newAK47
}
func main() {
	ak47 := NewAK47(30)
	add := NewAK47(20)
	(())
	ak47 = (add)
	(())
}

As you can see, we passAddBulletThe function increases the gun's bullet, but does not actually change any member variables in the incoming structure. Finally, a new one with the update field is returnedAK47Structure.

Compared with copying values, pointers have more advantages, especially when your structure member variables and content are very large, this method, modifying data through copying, may cause performance problems. You should ask yourself, is it worth it, for example, you are writing concurrent code?

Summarize

When you use invariants, be sure to weigh the pros and cons first. Implementing the method described in this blog requires a lot of code. However, if we do not consider the immutability of shared variables when writing concurrent code, we often encounter situations that are not consistent with expectations, such as memory race problems? Actually, what I want to talk about is thread safety issues : - )

Implementing invariance may also cause serious performance problems! This is a double-edged sword. Please do not optimize the code too early.

The above is the detailed content of Go through the invariance optimization program. For more information about the invariance of Go program, please pay attention to my other related articles!