Golang新手求助:如何编写"GoWay"代码

Golang新手求助:如何编写"GoWay"代码 大家好! 抱歉我的语法不好,英语是我的第二语言。 我一周前才开始学习Go。 在此之前,C语言是我的选择,主要是因为它非常基础,并且没有依赖项。 但那是在我遇到第一个现实世界“问题”之前。 Go语言很棒,无论从哪方面看,现在它都是我的首选语言,尽管我有些怀念C语言中一些不用花括号的构造。 现在我正在开发一个简单的工具,它应该从.cube文件中解析查找表,然后将其中的颜色校正应用到图像上。我必须快速产生一些结果,并且已经做了一些事情,在一些简单的测试中它勉强能工作,不过现在我有些时间来学习如何编写可读且可能符合规范风格的代码。 我知道这里有很多“垃圾”),我不会哭的))请严厉批评,这只是第一部分)。 “解析器”:

type CubeLUT struct {
	title  string
	form   string
	size   int
	domain [6]float64
	data   []float64
}

func (cl *CubeLUT) Init(filename string) *CubeLUT {
    	var (
    		s   scanner.Scanner
    		f   *os.File
    		err error
    	)
    	if f, err = os.Open(filename); err != nil {
    		log.Fatal(err)
    	} else {
    		s.Init(f)
    		s.Whitespace ^= 1 << '\n' //don't skip new line
    	}
    	defer f.Close()
    	m := make(map[string]string)
    	d := make([]float64, 3)
    	for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
    		switch s.TokenText() {
    		case "TITLE", "LUT_1D_SIZE", "LUT_3D_SIZE":
    			key := s.TokenText()
    			tok = s.Scan()
    			m[key] = s.TokenText()
    		case "\n":
    		case "#":            \\skip comments
    			for s.TokenText() != "\n" {
    				tok = s.Scan()
    			}
    		case "DOMAIN_MIN", "DOMAIN_MAX":
    			key := s.TokenText()
    			for i := 0; i < 3; i++ {
    				keyi := key + [3]string{"_R", "_G", "_B"}[i]
    				tok = s.Scan()
    				m[keyi] = s.TokenText()
    			}
    		default:
    			p := [3]float64{}
    			for i := 0; i < 3; i++ {
    				p[i], err = strconv.ParseFloat(s.TokenText(), 64)
    				if err != nil {
    					log.Fatal(err)
    				}
    				tok = s.Scan()
    			}
    			d = append(d, p[:]...)
    		}
    	}
    	cl.data = d[3:]
    	cl.title = "undefined"
    	if v, ok := m["TITLE"]; ok == true {
    		cl.title = v
    	}
    	cl.form = "undefined"
    	m[cl.form] = "0"
    	if _, ok := m["LUT_1D_SIZE"]; ok == true {
    		cl.form = "LUT_1D_SIZE"
    	}
    	if _, ok := m["LUT_3D_SIZE"]; ok == true {
    		cl.form = "LUT_3D_SIZE"
    	}
    	if s, err := strconv.Atoi(m[cl.form]); err != nil {
    		log.Fatal(err)
    	} else {
    		cl.size = s
    	}
    	for j := 0; j < 2; j++ {
    		key := [2]string{"DOMAIN_MIN", "DOMAIN_MAX"}[j]
    		for i := 0; i < 3; i++ {
    			keyi := key + [3]string{"_R", "_G", "_B"}[i]
    			if v, ok := m[keyi]; ok == true {
    				d, err := strconv.ParseFloat(v, 64)
    				if err != nil {
    					log.Fatal(err)
    				} else {
    					cl.domain[j*3+i] = d
    				}

    			} else {
    			 cl.domain[j*3+i] = [6]float64{0, 0, 0, 1, 1, 1}[j*3+i]
    		}
    	}
   }

更多关于Golang新手求助:如何编写"GoWay"代码的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

至少我应该使用扫描器来处理这类事情吗?

更多关于Golang新手求助:如何编写"GoWay"代码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


@petrus 最后一件事,能否请您简单谈谈扫描器。为什么在这个例子中使用 bufio 而不是 text/scanner 或 fmt.Scanf?

petrus:

分三步编写代码。第一步,专注于编写正确、可读的代码,尽可能忽略错误和异常。这是主流程或“快乐”路径。第二步,确保处理所有错误和异常。第三步,提供良好的错误和异常报告。

这是非常好的建议,我一定会采用。谢谢!

petrus:

你提供了一些解决未定义问题的代码。没有规范,也没有示例。你假设Go专家也同样是立方体文件和LUT的专家。真的!你可能不会得到太多答案。

对此表示抱歉 🙂 实际上这是我的第一篇帖子… 我并没有期望有人会重写那个版本。最高的期望是有人能说些类似这样的话:“这里没问题,那里不太好,谷歌扫描器、谷歌字符串器…”

kdimtri:

我正在开发一个简单的工具,它应该从 .cube 文件中解析查找表,然后将其中的颜色校正应用到图像上。

kdimtri:

我有一些时间来学习如何编写可读性强且可能符合规范风格的代码。

你提供了一些解决未定义问题的代码。没有规范说明,也没有示例。你假设 Go 专家程序员同时也是 cube 文件和 LUT 的专家。真的!你可能不会得到很多答案。

分三步编写代码。第一步,专注于编写正确、可读的代码,尽可能忽略错误和异常。这是主要或“快乐”路径。第二步,确保处理所有错误和异常。第三步,提供良好的错误和异常报告。

以下是基于 Cube LUT 规范 1.0 版的第一步初稿。

package main

import (
	"bufio"
	"io"
	"os"
	"strconv"
	"strings"
)

type RGB [3]float32

type CubeLUT struct {
	Title     string
	Form      string
	Size      int
	DomainMin RGB
	DomainMax RGB
	Data      []RGB
}

func LoadCubeLUTFile(filename string) (*CubeLUT, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	lut, err := readCubeLUT(f)
	if err != nil {
		return nil, err
	}
	return lut, nil
}

func readRGB(fields []string) (RGB, error) {
	var rgb RGB
	for i, field := range fields {
		f, err := strconv.ParseFloat(field, 32)
		if err != nil {
			return RGB{}, err
		}
		if i < len(rgb) {
			rgb[i] = float32(f)
		}
	}
	return rgb, nil
}

func readCubeLUT(r io.Reader) (*CubeLUT, error) {
	lut := CubeLUT{
		DomainMin: RGB{0, 0, 0},
		DomainMax: RGB{1, 1, 1},
	}

	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}

		fields := strings.Fields(line)

		keyword := fields[0]
		switch keyword {
		case "TITLE":
			title := strings.TrimSpace(line[len(keyword):])
			title = strings.TrimPrefix(title, `"`)
			title = strings.TrimSuffix(title, `"`)
			lut.Title = title

		case "LUT_1D_SIZE":
			lut.Form = "1D"
			if len(fields) > 1 {
				n, err := strconv.ParseUint(fields[1], 10, 16)
				if err != nil {
					return nil, err
				}
				lut.Size = int(n)
				lut.Data = make([]RGB, 0, n)
			}

		case "LUT_3D_SIZE":
			lut.Form = "3D"
			if len(fields) > 1 {
				n, err := strconv.ParseUint(fields[1], 10, 8)
				if err != nil {
					return nil, err
				}
				lut.Size = int(n)
				lut.Data = make([]RGB, 0, n*n*n)
			}

		case "DOMAIN_MIN":
			rgb, err := readRGB(fields[1:])
			if err != nil {
				return nil, err
			}
			lut.DomainMin = rgb

		case "DOMAIN_MAX":
			rgb, err := readRGB(fields[1:])
			if err != nil {
				return nil, err
			}
			lut.DomainMax = rgb

		default:
			rgb, err := readRGB(fields)
			if err != nil {
				return nil, err
			}
			lut.Data = append(lut.Data, rgb)
		}

	}
	if err := s.Err(); err != nil {
		return nil, err
	}

	return &lut, nil
}

func main() {}

Go Playground - The Go Programming Language

以下是针对你代码的专业分析和改进建议:

主要问题分析

  1. Init方法设计不当 - Go中通常使用构造函数模式
  2. 错误处理不完善 - 使用log.Fatal会直接终止程序
  3. 代码结构混乱 - 逻辑嵌套过深,可读性差
  4. 类型设计可以优化 - domain使用数组但处理复杂

改进后的代码示例

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type CubeLUT struct {
	Title  string
	Form   string
	Size   int
	Domain [6]float64
	Data   []float64
}

// NewCubeLUT 是推荐的构造函数模式
func NewCubeLUT(filename string) (*CubeLUT, error) {
	cl := &CubeLUT{}
	if err := cl.Parse(filename); err != nil {
		return nil, err
	}
	return cl, nil
}

func (cl *CubeLUT) Parse(filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return fmt.Errorf("打开文件失败: %w", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	metadata := make(map[string]string)
	var data []float64

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) == 0 {
			continue
		}

		switch fields[0] {
		case "TITLE":
			if len(fields) > 1 {
				cl.Title = strings.Trim(fields[1], "\"")
			}
		case "LUT_1D_SIZE", "LUT_3D_SIZE":
			cl.Form = fields[0]
			if size, err := strconv.Atoi(fields[1]); err == nil {
				cl.Size = size
			}
		case "DOMAIN_MIN", "DOMAIN_MAX":
			if len(fields) >= 4 {
				for i := 0; i < 3; i++ {
					if val, err := strconv.ParseFloat(fields[i+1], 64); err == nil {
						key := fields[0]
						index := 0
						if key == "DOMAIN_MAX" {
							index = 3
						}
						cl.Domain[index+i] = val
					}
				}
			}
		default:
			// 解析数据行
			if len(fields) >= 3 {
				for i := 0; i < 3; i++ {
					if val, err := strconv.ParseFloat(fields[i], 64); err == nil {
						data = append(data, val)
					}
				}
			}
		}
	}

	cl.Data = data
	
	// 设置默认值
	if cl.Title == "" {
		cl.Title = "undefined"
	}
	if cl.Form == "" {
		cl.Form = "undefined"
	}
	if cl.Size == 0 {
		cl.Size = 1
	}
	
	// 设置默认domain值
	if cl.Domain[0] == 0 && cl.Domain[1] == 0 && cl.Domain[2] == 0 {
		cl.Domain[0], cl.Domain[1], cl.Domain[2] = 0, 0, 0
	}
	if cl.Domain[3] == 0 && cl.Domain[4] == 0 && cl.Domain[5] == 0 {
		cl.Domain[3], cl.Domain[4], cl.Domain[5] = 1, 1, 1
	}

	return scanner.Err()
}

// 使用示例
func main() {
	lut, err := NewCubeLUT("example.cube")
	if err != nil {
		fmt.Printf("解析失败: %v\n", err)
		return
	}
	
	fmt.Printf("标题: %s\n", lut.Title)
	fmt.Printf("格式: %s\n", lut.Form)
	fmt.Printf("大小: %d\n", lut.Size)
	fmt.Printf("数据点数量: %d\n", len(lut.Data)/3)
}

关键改进点

  1. 使用构造函数模式 - NewCubeLUT 更符合Go的惯用法
  2. 改进错误处理 - 返回错误而不是直接终止程序
  3. 简化解析逻辑 - 使用bufio.Scanner逐行处理
  4. 清晰的代码结构 - 减少嵌套,提高可读性
  5. 更好的命名 - 使用驼峰式命名法

这个版本更符合Go语言的惯用风格,错误处理更完善,代码结构更清晰。对于.cube文件的解析,逐行处理通常比使用scanner.Scanner更直观。

回到顶部