Go error 处理最佳实践

发表于 3年以前  | 总阅读数:345 次

今天分享 go 语言 error 处理的最佳实践,了解当前 error 的缺点、妥协以及使用时注意事项。文章内容较长,干货也多,建义收藏

什么是 error

大家都知道 error[1] 是源代码内嵌的接口类型。根据导出原则,只有大写的才能被其它源码包引用,但是 error 属于 predeclared identifiers 预定义的,并不是关键字,细节参考[int make 居然不是关键字?]

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
 Error() string
}

error 只有一个方法 Error() string 返回错误消息

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
 return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
 s string
}

func (e *errorString) Error() string {
 return e.s
}

一般我们创建 error 时只需要调用 errors.New("error from somewhere") 即可,底层就是一个字符串结构体 errorStrings

当前 error 有哪些问题

func Test() error {
 if err := func1(); err != nil {
  return err
 }
  ......
}

这是常见的用法,也最被人诟病,很多人觉得不如 try-catch 用法简洁,有人戏称 go 源码错误处理占一半

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except BaseException as err:
    print(f"Unexpected {err=}, {type(err)=}")
    raise

比如上面是 python try-catch 的用法,先写一堆逻辑,不处理异常,最后统一捕获

let mut cfg = self.check_and_copy()?;

相比来说 rust Result 模式更简洁,一个 ? 就代替了我们的操作。但是 error 的繁琐判断是当前的痛点嘛?显然不是,尤其喜欢 c 语言的人,反而喜欢每次都做判断

在我看来 go 的痛点不是缺少泛型,不是 error 太挫,而是 GC 太弱,尤其对大内存非常不友好,这方面可以参考[真实环境下大内存 Go 服务性能优化一例]

当前 error 的问题有两点:

  1. 无法 wrap 更多的信息,比如调用栈,比如层层封装的 error 消息
  2. 无法很好的处理类型信息,比如我想知道错误是 io 类型的,还是 net 类型的

1.Wrap 更多的消息

这方面有很多轮子,最著名的就是 https://github.com/pkg/errors, 我司也重度使用,主要功能有三个:

  1. Wrap 封装底层 error, 增加更多消息,提供调用栈信息,这是原生 error 缺少的
  2. WithMessage 封装底层 error, 增加更多消息,但不提供调用栈信息
  3. Cause 返回最底层的 error, 剥去层层的 wrap
import (
   "database/sql"
   "fmt"

   "github.com/pkg/errors"
)

func foo() error {
   return errors.Wrap(sql.ErrNoRows, "foo failed")
}

func bar() error {
   return errors.WithMessage(foo(), "bar failed")
}

func main() {
   err := bar()
   if errors.Cause(err) == sql.ErrNoRows {
      fmt.Printf("data not found, %v\n", err)
      fmt.Printf("%+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/*Output:
data not found, bar failed: foo failed: sql: no rows in result set
sql: no rows in result set
foo failed
main.foo
    /usr/three/main.go:11
main.bar
    /usr/three/main.go:15
main.main
    /usr/three/main.go:19
runtime.main
    ...
*/

这是测试代码,当用 %v 打印时只有原始错误信息,%+v 时打印完整调用栈。当 go1.13 后,标准库 errors 增加了 Wrap 方法

func ExampleUnwrap() {
 err1 := errors.New("error1")
 err2 := fmt.Errorf("error2: [%w]", err1)
 fmt.Println(err2)
 fmt.Println(errors.Unwrap(err2))
 // Output
 // error2: [error1]
 // error1
}

标准库没有提供增加调用栈的方法,fmt.Errorf 指定 %w 时可以 wrap error, 但整体来讲,并没有 https://github.com/pkg/errors 库好用

2.错误类型

这个例子来自 ITNEXT[2]

import (
   "database/sql"
   "fmt"
)

func foo() error {
   return sql.ErrNoRows
}

func bar() error {
   return foo()
}

func main() {
   err := bar()
   if err == sql.ErrNoRows {
      fmt.Printf("data not found, %+v\n", err)
      return
   }
   if err != nil {
      // Unknown error
   }
}
//Outputs:
// data not found, sql: no rows in result set

有时我们要处理类型信息,比如上面例子,判断 err 如果是 sql.ErrNoRows 那么视为正常,data not found 而己,类似于 redigo 里面的 redigo.Nil 表示记录不存在

func foo() error {
   return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}

但是如果 foo 把 error 做了一层 wrap 呢?这个时候错误还是 sql.ErrNoRows 嘛?肯定不是,这点没有 python try-catch 错误处理强大,可以根据不同错误 class 做出判断。那么 go 如何解决呢?答案是 go1.13 新增的 Is[3] 和 As

import (
   "database/sql"
   "errors"
   "fmt"
)

func bar() error {
   if err := foo(); err != nil {
      return fmt.Errorf("bar failed: %w", foo())
   }
   return nil
}

func foo() error {
   return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
}

func main() {
   err := bar()
   if errors.Is(err, sql.ErrNoRows) {
      fmt.Printf("data not found,  %+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/* Outputs:
data not found,  bar failed: foo failed: sql: no rows in result set
*/

还是这个例子,errors.Is 会递归的 Unwrap err, 判断错误是不是 sql.ErrNoRows,这里个小问题,Is 是做的指针地址判断,如果错误 Error() 内容一样,但是根 error 是不同实例,那么 Is 判断也是 false, 这点就很扯

func ExampleAs() {
 if _, err := os.Open("non-existing"); err != nil {
  var pathError *fs.PathError
  if errors.As(err, &pathError) {
   fmt.Println("Failed at path:", pathError.Path)
  } else {
   fmt.Println(err)
  }
 }

 // Output:
 // Failed at path: non-existing
}

errors.As[4] 判断这个 err 是否是 fs.PathError 类型,递归调用层层查找,源码后面再讲解

另外一个判断类型或是错误原因的就是 https://github.com/pkg/errors 库提供的 errors.Cause

switch err := errors.Cause(err).(type) {
case *MyError:
        // handle specifically
default:
        // unknown error
}

在没有 Is``As 类型判断时,需要很恶心的去判断错误自符串

func (conn *cendolConnectionV5) serve() {
 // Buffer needs to be preserved across messages because of packet coalescing.
 reader := bufio.NewReader(conn.Connection)
 for {
  msg, err := conn.readMessage(reader)
  if err != nil {
   if netErr, ok := strings.Contain(err.Error(), "temprary"); ok   {
     continue
   }
  }

  conn.processMessage(msg)
 }
}

想必接触 go 比较早的人一定很熟悉,如果 conn 从网络接受到的连接错误是 temporary 临时的那么可以 continue 重试,当然最好 backoff sleep 一下

当然现在新增加了 net.Error 类型,实现了 Temporary 接口,不过也要废弃了,请参考#45729[5]

源码实现

1.github.com/pkg/errors 库如何生成 warapper error

// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
 if err == nil {
  return nil
 }
 err = &withMessage{
  cause: err,
  msg:   message,
 }
 return &withStack{
  err,
  callers(),
 }
}

主要的函数就是 Wrap, 代码实现比较简单,查看如何追踪调用栈可以查看源码

2.github.com/pkg/errorsCause 实现

type withStack struct {
 error
 *stack
}

func (w *withStack) Cause() error { return w.error }

func Cause(err error) error {
 type causer interface {
  Cause() error
 }

 for err != nil {
  cause, ok := err.(causer)
  if !ok {
   break
  }
  err = cause.Cause()
 }
 return err
}

Cause 递归调用,如果没有实现 causer 接口,那么就返回这个 err

3.官方库如何生成一个 wrapper error

官方没有这样的函数,而是 fmt.Errorf 格式化时使用 %w

e := errors.New("this is a error")
w := fmt.Errorf("more info about it %w", e)
func Errorf(format string, a ...interface{}) error {
 p := newPrinter()
 p.wrapErrs = true
 p.doPrintf(format, a)
 s := string(p.buf)
 var err error
 if p.wrappedErr == nil {
  err = errors.New(s)
 } else {
  err = &wrapError{s, p.wrappedErr}
 }
 p.free()
 return err
}

func (p *pp) handleMethods(verb rune) (handled bool) {
 if p.erroring {
  return
 }
 if verb == 'w' {
  // It is invalid to use %w other than with Errorf, more than once,
  // or with a non-error arg.
  err, ok := p.arg.(error)
  if !ok || !p.wrapErrs || p.wrappedErr != nil {
   p.wrappedErr = nil
   p.wrapErrs = false
   p.badVerb(verb)
   return true
  }
  p.wrappedErr = err
  // If the arg is a Formatter, pass 'v' as the verb to it.
  verb = 'v'
 }
  ......
}

代码也不难,handleMethods 时特殊处理 w, 使用 wrapError 封装一下即可

4.官方库 Unwrap 实现

func Unwrap(err error) error {
 u, ok := err.(interface {
  Unwrap() error
 })

 if !ok {
  return nil
 }
 return u.Unwrap()
}

也是递归调用,否则接口断言失败,返回 nil

type wrapError struct {
 msg string
 err error
}

func (e *wrapError) Error() string {
 return e.msg
}

func (e *wrapError) Unwrap() error {
 return e.err
}

上文 fmt.Errof 时生成的 error 结构体如上所示,Unwrap 直接返回底层 err

5.官方库 Is``As 实现

本段源码分析来自 flysnow[6]

func Is(err, target error) bool {
 if target == nil {
  return err == target
 }

 isComparable := reflectlite.TypeOf(target).Comparable()

 //for循环,把err一层层剥开,一个个比较,找到就返回true
 for {
  if isComparable && err == target {
   return true
  }
  //这里意味着你可以自定义error的Is方法,实现自己的比较代码
  if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
   return true
  }
  //剥开一层,返回被嵌套的err
  if err = Unwrap(err); err == nil {
   return false
  }
 }
}

Is 函数比较简单,递归层层检查,如果是嵌套 err, 那就调用 Unwrap 层层剥开找到最底层 err, 最后判断指针是否相等

var errorType = reflectlite.TypeOf((*error)(nil)).Elem()

func As(err error, target interface{}) bool {
    //一些判断,保证target,这里是不能为nil
 if target == nil {
  panic("errors: target cannot be nil")
 }
 val := reflectlite.ValueOf(target)
 typ := val.Type()

 //这里确保target必须是一个非nil指针
 if typ.Kind() != reflectlite.Ptr || val.IsNil() {
  panic("errors: target must be a non-nil pointer")
 }

 //这里确保target是一个接口或者实现了error接口
 if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {
  panic("errors: *target must be interface or implement error")
 }
 targetType := typ.Elem()
 for err != nil {
     //关键部分,反射判断是否可被赋予,如果可以就赋值并且返回true
     //本质上,就是类型断言,这是反射的写法
  if reflectlite.TypeOf(err).AssignableTo(targetType) {
   val.Elem().Set(reflectlite.ValueOf(err))
   return true
  }
  //这里意味着你可以自定义error的As方法,实现自己的类型断言代码
  if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
   return true
  }
  //这里是遍历error链的关键,不停的Unwrap,一层层的获取err
  err = Unwrap(err)
 }
 return false
}

代码同样是递归调用 As, 同时 Unwrap 最底层的 error, 然后用反射判断是否可以赋值,如果可以,那么说明是同一类型

ErrGroup 使用

提到 error 就必须要提一下 golang.org/x/sync/errgroup, 适用如下场景:并发场景下,如果一个 goroutine 有错误,那么就要提前返回,并取消其它并行的请求

func ExampleGroup_justErrors() {
 g := new(errgroup.Group)
 var urls = []string{
  "http://www.golang.org/",
  "http://www.google.com/",
  "http://www.somestupidname.com/",
 }
 for _, url := range urls {
  // Launch a goroutine to fetch the URL.
  url := url // https://golang.org/doc/faq#closures_and_goroutines
  g.Go(func() error {
   // Fetch the URL.
   resp, err := http.Get(url)
   if err == nil {
    resp.Body.Close()
   }
   return err
  })
 }
 // Wait for all HTTP fetches to complete.
 if err := g.Wait(); err == nil {
  fmt.Println("Successfully fetched all URLs.")
 }
}

上面是官方给的例子,底层使用 context 来 cancel 其它请求,同步使用 WaitGroup, 原理非常简单,代码量非常少,感兴趣的可以看源码

这里一定要注意三点:

  1. context 是谁传进来的?其它代码会不会用到,cancel 只能执行一次,瞎比用会出问题
  2. g.Go 不带 recover 的,为了程序的健壮,一定要自行 recover
  3. 并行的 goroutine 有一个错误就返回,而不是普通的 fan-out 请求后收集结果

线上实践注意的几个问题

1.error 与 panic

查看 go 源代码会发现,源码很多地方写 panic, 但是工程实践,尤其业务代码不要主动写 panic

理论上 panic 只存在于 server 启动阶段,比如 config 文件解析失败,端口监听失败等等,所有业务逻辑禁止主动 panic

根据 CAP 理论,当前 web 互联网最重要的是 AP, 高可用性才最关键(非银行金融场景),程序启动时如果有部分词表,元数据加载失败,都不能 panic, 提供服务才最关键,当然要有报警,让开发第一时间感知当前服务了的 QOS 己经降低

最后说一下,所有异步的 goroutine 都要用 recover 去兜底处理

2.错误处理与资源释放

func worker(done chan error) {
    err := doSomething()
    result := &result{}
    if err != nil {
        result.Err = err
    }
    done <- result
}

一般异步组装数据,都要分别启动 goroutine, 然后把结果通过 channel 返回,result 结构体拥有 err 字段表示错误

这里要注意,main 函数中 done channel 千万不能 close, 因为你不知道 doSomething 会超时多久返回,写 closed channel 直接 panic

所以这里有一个准则:数据传输和退出控制,需要用单独的 channel 不能混, 我们一般用 context 取消异步 goroutine, 而不是直接 close channels

3.error 级联使用问题

package main

import "fmt"

type myError struct {
 string
}

func (i *myError) Error() string {
 return i.string
}

func Call1() error {
 return nil
}

func Call2() *myError {
 return nil
}

func main() {
 err := Call1()
 if err != nil {
  fmt.Printf("call1 is not nil: %v\n", err)
 }

 err = Call2()
 if err != nil {
  fmt.Printf("call2 err is not nil: %v\n", err)
 }
}

这个问题非常经典,如果复用 err 变量的情况下, Call2 返回的 error 是自定义类型,此时 err 类型是不一样的,导致经典的 error is not nil, but value is nil

非常经典的 Nil is not nil[7] 问题。解决方法就是 Call2 err 重新定义一个变量,当然最简单就是统一 error 类型。有点难,尤其是大型项目

4.并发问题

go 内置类型除了 channel 大部分都是非线程安全的,error 也不例外,先看一个例子

package main
import (
   "fmt"
   "github.com/myteksi/hystrix-go/hystrix"
   "time"
)
var FIRST error = hystrix.CircuitError{Message:"timeout"}
var SECOND error = nil
func main() {
   var err error
   go func() {
      i := 1
      for {
         i = 1 - i
         if i == 0 {
            err = FIRST
         } else {
            err = SECOND
         }
         time.Sleep(10)
      }
   }()
   for {
      if err != nil {
         fmt.Println(err.Error())
      }
      time.Sleep(10)
   }
}

运行之前,大家先猜下会发生什么???

zerun.dong$ go run panic.go
hystrix: timeout
panic: value method github.com/myteksi/hystrix-go/hystrix.CircuitError.Error called using nil *CircuitError pointer

goroutine 1 [running]:
github.com/myteksi/hystrix-go/hystrix.(*CircuitError).Error(0x0, 0xc0000f4008, 0xc000088f40)
 <autogenerated>:1 +0x86
main.main()
 /Users/zerun.dong/code/gotest/panic.go:25 +0x82
exit status 2

上面是测试的例子,只要跑一会,就一定发生 panic, 本质就是 error 接口类型不是并发安全的

// 没有方法的interface
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
// 有方法的interface
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

所以不要并发对 error 赋值

5.error 要不要忽略

func Test(){
 _ = json.Marshal(xxxx)
 ......
}

有的同学会有疑问,error 是否一定要处理?其实上面的 Marshal 都有可能失败的

如果换成其它函数,当前实现可以忽略,不能保证以后还是兼容的逻辑,一定要处理 error,至少要打日志

6.errWriter

本例来自官方 blog[8], 有时我们想做 pipeline 处理,需要把 err 当成结构体变量

_, err = fd.Write(p0[a:b])
if err != nil {
    return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
    return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
    return err
}
// and so on

上面是原始例子,需要一直做 if err != nil 的判断,官方优化的写法如下

type errWriter struct {
    w   io.Writer
    err error
}

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}

// 使用时
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

清晰简洁,大家平时写代码可以多考滤一下

7.何时打印调用栈

官方库无法 wrap 调用栈,所以 fmt.Errorf %w 不如 pkg/errors 库实用,但是errors.Wrap 最好保证只调用一次,否则全是重复的调用栈

我们项目的使用情况是 log error 级别的打印栈,warn 和 info 都不打印,当然 case by case 还得看实际使用情况

8.Wrap前做判断

errors.Wrap(err, "failed")

通过查看源码,如果 err 为 nil 的时候,也会返回 nil. 所以 Wrap 前最好做下判断,建议来自 xiaorui.cc

小结

上面提到的线上实践注意的几个问题,都是实际发生的坑,惨痛的教训,大家一定要多体会下。错误处理涵盖内容非常广,本文不涉及分布式系统的错误处理、gRPC 错误传播以及错误管理

参考资料

[1] builting.go error interface: https://github.com/golang/go/blob/master/src/builtin/builtin.go#L260,

[2] ITNEXT: https://itnext.io/golang-error-handling-best-practice-a36f47b0b94c,

[3] errors.Is: https://github.com/golang/go/blob/master/src/errors/wrap.go#L40,

[4] errors.As example: https://github.com/golang/go/blob/master/src/errors/wrap_test.go#L255,

[5] #45729: https://github.com/golang/go/issues/45729,

[6] flysnow error 分析: https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html,

[7] Nil is not nil: https://yourbasic.org/golang/gotcha-why-nil-error-not-equal-nil/,

[8] errors are values: https://blog.golang.org/errors-are-values,

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/XojOIIZfKm_wXul9eSU1tQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 6年以前  |  237464次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8416次阅读
 目录