Golang中默认参数和嵌套结构体的推断问题

Golang中默认参数和嵌套结构体的推断问题 在使用Go和TypeScript体验Pulumi后,我对Go语言缺乏可选/默认参数以及无法推断嵌套类型的问题有一些思考,很想知道社区对此的看法。

以下是一个在Go和TypeScript中完全相同的Pulumi项目,用于在K8s中启动一个Nginx部署。

import * as k8s from "@pulumi/kubernetes";

const appLabels = { app: "nginx" };
const deployment = new k8s.apps.v1.Deployment("nginx", {
    spec: {
        selector: { matchLabels: appLabels },
        replicas: 1,
        template: {
            metadata: { labels: appLabels },
            spec: { containers: [{ name: "nginx", image: "nginx" }] }
        }
    }
});
export const name = deployment.metadata.name;
package main

import (
    appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/apps/v1"
    corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/core/v1"
    metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/meta/v1"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {

        appLabels := pulumi.StringMap{
            "app": pulumi.String("nginx"),
        }
        deployment, err := appsv1.NewDeployment(ctx, "app-dep", &appsv1.DeploymentArgs{
            Spec: appsv1.DeploymentSpecArgs{
                Selector: &metav1.LabelSelectorArgs{
                    MatchLabels: appLabels,
                },
                Replicas: pulumi.Int(1),
                Template: &corev1.PodTemplateSpecArgs{
                    Metadata: &metav1.ObjectMetaArgs{
                        Labels: appLabels,
                    },
                    Spec: &corev1.PodSpecArgs{
                        Containers: corev1.ContainerArray{
                            corev1.ContainerArgs{
                                Name:  pulumi.String("nginx"),
                                Image: pulumi.String("nginx"),
                            }},
                    },
                },
            },
        })
        if err != nil {
            return err
        }

        ctx.Export("name", deployment.Metadata.Elem().Name())

        return nil
    })
}

TypeScript版本清晰得多,以至于无法选择Go作为此类IaaC项目的后端语言。我希望有人看到代码时能立刻明白他们在看什么。

这里存在两个问题:

  1. Pulumi大量使用了可选参数的变通方法,因此你必须使用将值转换为引用的“技巧”,以便它可以为nil。在我看来,这并不是糟糕的设计,而是一个希望拥有合理默认值的API的良好选择,这样你只需更改需要更改的部分。
  2. 嵌套结构体无法被推断,因此你必须为每个嵌套结构体命名,这在一个需要更长描述性结构体名称的大型项目中会导致大量杂乱。

这类情况非常普遍,例如使用像AWS这样的云服务提供商API时。我看到GCP在Go方面有专长,因此拥有更扁平的结构,并且设计方式上不需要使用可选参数的技巧,但像GKE这样的服务仍然存在深度嵌套且杂乱的结构体。

我非常喜欢Go的简洁性,我现在使用的几乎所有云工具都是用Go编写的,我也希望将它用于IaaC,但目前TypeScript是更好的选择。我认为这两点虽然给API设计者增加了一点复杂性,但却极大地降低了API使用者的复杂性。


更多关于Golang中默认参数和嵌套结构体的推断问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中默认参数和嵌套结构体的推断问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go语言确实没有可选参数和默认参数的语言特性,这导致在需要处理大量可选字段的API(如云服务SDK)中,代码会显得冗长。不过,我们可以通过一些模式来改善这种情况。

对于嵌套结构体的推断问题,虽然Go不支持类型推断,但可以通过结构体字面量的方式减少类型重复。以下是一些改进示例:

1. 使用辅助函数简化可选字段

// 为常用类型创建辅助函数
func PtrInt(v int) *int { return &v }
func PtrString(v string) *string { return &v }
func PtrBool(v bool) *bool { return &v }

// 使用示例
deploymentArgs := &appsv1.DeploymentArgs{
    Spec: &appsv1.DeploymentSpecArgs{
        Replicas: PtrInt(1),
        // 其他字段...
    },
}

2. 使用构建器模式

type DeploymentBuilder struct {
    args *appsv1.DeploymentArgs
}

func NewDeploymentBuilder(name string) *DeploymentBuilder {
    return &DeploymentBuilder{
        args: &appsv1.DeploymentArgs{
            Metadata: &metav1.ObjectMetaArgs{
                Name: PtrString(name),
            },
            Spec: &appsv1.DeploymentSpecArgs{},
        },
    }
}

func (b *DeploymentBuilder) WithReplicas(replicas int) *DeploymentBuilder {
    b.args.Spec.Replicas = PtrInt(replicas)
    return b
}

func (b *DeploymentBuilder) Build() *appsv1.DeploymentArgs {
    return b.args
}

// 使用示例
deploymentArgs := NewDeploymentBuilder("nginx").
    WithReplicas(3).
    Build()

3. 使用函数选项模式

type DeploymentOption func(*appsv1.DeploymentArgs)

func WithReplicas(replicas int) DeploymentOption {
    return func(args *appsv1.DeploymentArgs) {
        args.Spec.Replicas = PtrInt(replicas)
    }
}

func WithLabels(labels map[string]string) DeploymentOption {
    return func(args *appsv1.DeploymentArgs) {
        if args.Metadata == nil {
            args.Metadata = &metav1.ObjectMetaArgs{}
        }
        args.Metadata.Labels = labels
    }
}

func NewDeployment(opts ...DeploymentOption) *appsv1.DeploymentArgs {
    args := &appsv1.DeploymentArgs{
        Spec: &appsv1.DeploymentSpecArgs{},
    }
    for _, opt := range opts {
        opt(args)
    }
    return args
}

// 使用示例
deploymentArgs := NewDeployment(
    WithReplicas(3),
    WithLabels(map[string]string{"app": "nginx"}),
)

4. 简化嵌套结构体初始化

// 使用局部变量和类型推断
appLabels := pulumi.StringMap{"app": pulumi.String("nginx")}

deploymentArgs := &appsv1.DeploymentArgs{
    Spec: &appsv1.DeploymentSpecArgs{
        Selector: &metav1.LabelSelectorArgs{MatchLabels: appLabels},
        Replicas: pulumi.Int(1),
        Template: &corev1.PodTemplateSpecArgs{
            Metadata: &metav1.ObjectMetaArgs{Labels: appLabels},
            Spec: &corev1.PodSpecArgs{
                Containers: corev1.ContainerArray{
                    {
                        Name:  pulumi.String("nginx"),
                        Image: pulumi.String("nginx"),
                    },
                },
            },
        },
    },
}

这些模式虽然不能完全达到TypeScript的可选参数和类型推断效果,但可以显著改善Go代码的可读性和编写体验。对于Pulumi这类工具,建议SDK提供者考虑内置这些简化模式。

回到顶部