围棋游乐场很无聊,所以我决定做一个小的应用程序,把我扔进语言。它基本上每秒钟都会查询聊天室中的新消息,并将它们显示在输出窗格中(减去机器人的消息)。
这对我来说都是新的,所以任何见解都是欢迎的。我特别不确定的一件事是我的return语句以及我是否正确地使用了它们。同样,有无数种不同的方法来声明变量;到目前为止,我是否做对了?
package main
import (
"encoding/json"
"fmt"
sanitize "github.com/kennygrant/sanitize"
"html"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
)
type ChatEvent struct {
Event_type int8
Time_stamp int64
Content string
User_id int64
User_name string
Message_id int64
Parent_id int64
Show_parent bool
Message_Edits int8
Message_Stars int8
}
type ChatEvents struct {
Events []ChatEvent
Time int64
Sync int64
Ms int8
}
const (
Mode = "Messages"
ChatUrl = "http://chat.stackexchange.com/chats/8595/events"
FKey = "68f5a3ce293c47399c96ffcd85e0d280" // Go to the chat, F12, reload, look at the /events call and steal its fkey property
MessageCount = 100 // Never use this value directly: SE only uses it as a hint and might return a bit more when it feels like it
Since = 0
)
var (
lastMessage int64
)
func main() {
ticker := time.NewTicker(1 * time.Second)
quit := make(chan struct{})
for {
select {
case <-ticker.C:
body := sendRequest()
parsedJson := parseJson(body)
newestMessages := getNewestMessages(parsedJson.Events)
for i := 0; i < len(newestMessages); i++ {
var currentMessage = newestMessages[i]
if currentMessage.User_id == -263 || currentMessage.User_id == 125580 {
// Ignore Captain Obvious & Duga
continue
}
fmt.Printf("[%v] %s: %s\n",
time.Unix(currentMessage.Time_stamp, 0).Format("15:04:05"),
currentMessage.User_name,
currentMessage.Content)
}
case <-quit:
ticker.Stop()
return
}
}
}
func sendRequest() (content []byte) {
response, err := http.PostForm(ChatUrl, url.Values{
"mode": {Mode},
"msgCount": {strconv.Itoa(MessageCount)},
"fkey": {FKey},
"since": {strconv.Itoa(Since)}})
if err != nil {
fmt.Println("Error retrieving response")
fmt.Println(err.Error())
return nil
}
defer response.Body.Close()
content, err = ioutil.ReadAll(response.Body)
if err != nil {
fmt.Println("Error reading response")
fmt.Println(err.Error())
return nil
}
return
}
func parseJson(body []byte) (parsedJson ChatEvents) {
err := json.Unmarshal(body, &parsedJson)
if err != nil {
fmt.Println("Error parsing json")
fmt.Println(err.Error())
return
}
return
}
func getNewestMessages(events []ChatEvent) (output []ChatEvent) {
output = make([]ChatEvent, 0)
for index := 0; index < len(events); index++ {
if events[index].Time_stamp > lastMessage {
events[index].Content = html.UnescapeString(sanitize.HTML(events[index].Content))
output = append(output, events[index])
lastMessage = events[index].Time_stamp
}
}
return
}发布于 2015-04-25 18:24:58
import sanitize "github.com/kennygrant/sanitize"最好写成简单明了的文字:
import "github.com/kennygrant/sanitize"因为重命名是多余的。
如前所述,更常见的做法是在Go中更加明确地处理错误,并让实用程序函数(如sendRequest)返回任何用于处理“更高”的错误。
除了有效围棋之外,我建议查看Go项目的代码评审注释页面。虽然后者只是Go作者使用的内容,并不一定是更广泛的应用,但我认为这是一个很好的起点。
除其他外,关于命名,它建议(同样如前所述)使用EventType、UserID、parsedJSON等,而不是您所使用的。此外,在命名问题上,我个人赞同罗斯·考克斯的命名哲学,它建议i vs index、msg与currentMessage以及e vs events[index]用于在短时间内使用的标识符(相对于文件或项目范围的标识符)。
在编组JSON时,使用struct标记不仅可以将Go样式名称映射到JSON样式名称,还可以应用其他属性(例如“省略”:FieldName string `json:"field_name,omitempty"`)。特别是,如果不这样做,如果您试图将您的结构编组回JSON,我相信您最终会得到您的JSON字段大写(因为Go的encoding/json包只能处理导出的字段)。
“代码评审注释”页面还建议对命名结果参数的使用实行警告/限制。它们对于通过godoc编写文档可能很有用,但我尽量避免它们,只是为了在正文中保存一个变量初始化行。您的兴趣可能不同,但是如果您使用它们,请小心跟踪命名的返回(例如,使用命名的err error,在if /中使用类似于x, err := func()的内容)。
与其使用像ioutil.ReadAll这样的东西来预读所有数据,我发现寻找基于“流”io.Reader的替代方案是很有用的。对于处理大型数据输入的程序(例如,逐段读取大文件),这可能会产生很大的不同。在这里,encoding/json为此提供了一个Decoder类型。在这种特殊情况下,我不知道它是否会产生任何内存/性能差异,但我发现使用它(并结合您的发送和解析函数)的结果似乎简化了代码。
回到编组,使用自定义(Un)编组创建自己的瘦包装类型有时会很有帮助/有用,而不是将自己限制在基本类型上。例如,在下面的代码中,我添加了一个setime类型,它从文档化的StackExchange API的方式中解封以完成所有时间/日期。如果还需要编组,您可以轻松地添加MarshalJSON方法。这里的区别并不重要,但例如,它允许更容易地使用所有time.Time方法/函数(例如,time.Since(msg.Timestamp.Time)、更改输出时区等)。
在几个地方,你做的事情如下:
for i := 0; i < len(newestMessages); i++ {
var currentMessage = newestMessages[i]
…
for index := 0; index < len(events); index++ {
// use events[index]这样做更符合自己的要求:
for _, msg := range newestMessages {
// use msg
…
for i := range events {
// use events[i] or
e := &events[i]
// and use e.Content, &e, etc甚至:
for _, e := range events {
// use e在这种情况下,最后一个表单将生成一个额外的副本(并使e.Content = fn(e.Content)行只修改副本)。这里我不担心额外的副本,但是您可以通过使用上面所示的e := &events[i]或将类型更改为[]*Events (尽管代价是产生更多的垃圾)来避免它。
您的main例程有一个不被使用的quit通道。如果这是为了将来的扩展,或者是用于If /当main变成一个可取消的函数时,那就没问题了。(不过,在创建之后立即使用defer ticker.Stop()可能会更符合惯例。)但是,通过删除未使用的通道,可以大大简化as-is main。
与其在代码中间检查一对显式用户ids,不如将它们移到列表或地图中。特别是,使用map[userIDType]bool允许检查仅为if ignoredUsers[msg.UserID] {continue}。
最后,对于像FKey这样的东西,我更喜欢在任何地方放置任何授权/API令牌,但使用硬编码常量。可选方案位于命令行、环境变量、配置文件中或其中的某些组合中。
哦,我不清楚API是什么,但也许您应该将请求中的“开始”字段设置为最新的时间戳或类似time.Since(latest) + fudgeDuration或somesuch之类的内容。
编辑:我做了这个改变,但忘了提到它。您很少需要/希望调用错误的Error()方法。特别是,fmt.Println(err.Error())是fmt.Println(err) (fmt同时处理fmt.Stringer,又名String(),以及error接口)的一种冗长的表达方式。我见过其他人做过像panic(err.Error())这样的事情,这比panic(err)更糟糕,因为尽管消息是相同的,但它会丢弃任何额外的信息/上下文(例如,丢失给试图使用recover的人)。
以下是我在应用上述内容后提出的建议。[也可以作为一个git克隆基,一次进行一些更改,这样您就可以看到一些中间的替代方案。]
package main
import (
"encoding/json"
"flag"
"fmt"
"html"
"log"
"net/http"
"net/url"
"os"
"strconv"
"time"
"github.com/kennygrant/sanitize"
)
type ChatEvent struct {
EventType int8 `json:"event_type"`
Timestamp setime `json:"time_stamp"`
Content string `json:"content"`
UserID int64 `json:"user_id"`
Username string `json:"user_name"`
MessageID int64 `json:"message_id"`
ParentID int64 `json:"parent_id"`
ShowParent bool `json:"show_parent"`
MessageEdits int8 `json:"message_edits"`
MessageStars int8 `json:"message_stars"`
}
type ChatEvents struct {
Events []ChatEvent
Time int64
Sync int64
Ms int8
}
const (
mode = "Messages"
chatURL = "http://chat.stackexchange.com/chats/8595/events"
// Never use this value directly: SE only uses it as a hint
// and might return a bit more when it feels like it
messageCount = 100
since = 0
)
var (
lastMessage time.Time
fkey = flag.String("fkey", "", `StackExchange chat "fkey"`)
ignoreUsers = map[int64]bool{
-263: true, // "Captain Obvious"
125580: true, // "Duga"
}
)
func main() {
flag.Parse()
if *fkey == "" {
fmt.Fprintln(os.Stderr, `missing required -fkey argument
Go to the chat, F12, reload, look at the /events call
and steal its fkey property`)
os.Exit(2) // 2 to match flag's exit code
}
for range time.Tick(time.Second) {
parsedJSON, err := fetchMessages()
if err != nil {
log.Println("fetching messages:", err)
// TODO, abort or backoff on repeated failures?
continue
}
newestMessages := getNewestMessages(parsedJSON.Events)
for _, msg := range newestMessages {
if ignoreUsers[msg.UserID] {
continue
}
fmt.Printf("[%v] %s: %s\n",
msg.Timestamp.Format("15:04:05"),
msg.Username,
msg.Content)
}
}
}
func fetchMessages() (ChatEvents, error) {
var parsedJSON ChatEvents
resp, err := http.PostForm(chatURL, url.Values{
"mode": {mode},
"msgCount": {strconv.Itoa(messageCount)},
"fkey": {*fkey},
"since": {strconv.Itoa(since)},
})
if err != nil {
return parsedJSON, err
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&parsedJSON)
return parsedJSON, err
}
func getNewestMessages(events []ChatEvent) []ChatEvent {
output := make([]ChatEvent, 0)
for _, e := range events {
if e.Timestamp.After(lastMessage) {
e.Content = html.UnescapeString(sanitize.HTML(e.Content))
output = append(output, e)
lastMessage = e.Timestamp.Time
}
}
return output
}
// setime is an unmarshable StackExchange timestamp or date.
// See https://api.stackexchange.com/docs/dates
type setime struct{ time.Time }
func (t *setime) UnmarshalJSON(b []byte) error {
var seconds int64
if err := json.Unmarshal(b, &seconds); err != nil {
return err
}
t.Time = time.Unix(seconds, 0)
return nil
}发布于 2015-04-25 06:29:32
免责声明:我不知道去哪。
sendRequest方法可能会失败,返回nil,但是main中的调用者继续愉快地继续,好像什么都没发生一样。事实上,从main调用的上下文来看,我的第一印象是sendRequest总是奇迹般地成功,但事实并非如此,因此有点误导。
类似地,parseJson也可能失败,什么也不解析,但是从您称之为它的地方来说,这并不明显,这也有一点误导。
共同公约似乎是用camelCase或PascalCase来命名,而不是下划线。因此,我建议重命名ChatEvent结构的字段。我甚至可以说,类型使用PascalCase,变量使用camelCase,但这在文档中并不那么清楚。
变量parsedJson没有说明它是什么类型的对象。所以我把它重命名为chatEvents。
这类用户的名单在全球范围内会更好:
如果currentMessage.User_id == -263 \{/忽略船长明显和杜加继续}
常量定义中的行尾注释使我滚动到最右边去阅读它们。更糟糕的是,当我进一步向下阅读代码时,当我看到文本右端附近的行时,水平滚动条的存在让我怀疑,如果我向右滚动,是否还有更多的东西。通常情况下没有什么,就像预期的那样,但这真的很烦人。如果行比最长的实际代码行宽,请避免行尾注释。或者更好的是,如果行尾注释将使行超过70个字符,则避免行尾注释.
https://codereview.stackexchange.com/questions/87897
复制相似问题