您正在受到监视
代码审查有一个开放的系统
一台机器每天每天都在监视您。
我知道是因为我构建了它。
我设计了该机器以检测要在Code Review上发布的建议,但它可以看到所有内容
涉及普通用户的可怕评论
像您这样的用户
Stack Exchange认为不相关的评论
他们不会采取行动所以我决定要
,但我需要一些合作伙伴
有常规技能的常客
主持人的挚爱,我们在聊天中工作
您可以轻松找到我们
但是在主题上或主题外,如果您的评论上升了
,我们会找到您
(兴趣介绍人,适应于兴趣评论)
相当长一段时间以来,Stack Overflow上的用户都发表了评论,指导人们在Code Review上发表评论。已经在Stack Overflow Meta上多次指出,用户在执行此操作时应格外小心。为了教育Stack Overflow用户,其中哪些帖子属于此处,哪些不属于,我决定将该功能包含在名为Duga的SE聊天机器人中。
该机器人在Spring MVC上运行环境。它使用Stack Exchange API每两分钟检查一次Stack Overflow注释。它扫描检索到的注释,并将其发布到第二监视器中,常规人员可以在其中检查“堆栈溢出”问题以确定其是否属于“代码审查”。当有我感兴趣的消息时,它还会在特殊的聊天室中发送一些信息-当最近的评论过多或重置神秘的费率配额时。 (我不希望它经常出现100条注释。到目前为止,两分钟之内从未发生过)。
如果要查看API结果的示例,可以使用此链接。 br />
整个机器人的Github存储库可以在这里找到
ScheduledTasks.java(它的相关部分)
@Autowired
private ChatBot chatBot;
@Autowired
private StackExchangeAPIBean stackAPI;
private Instant nextFetch = Instant.now();
private long lastComment;
private long fromDate;
private int remainingQuota;
private final WebhookParameters params = WebhookParameters.toRoom("8595");
private final WebhookParameters debug = WebhookParameters.toRoom("20298");
@Scheduled(cron = "0 */2 * * * *") // second minute hour day day day
public void scanComments() {
if (!Instant.now().isAfter(nextFetch)) {
return;
}
try {
StackComments comments = stackAPI.fetchComments("stackoverflow", fromDate);
int currentQuota = comments.getQuotaRemaining();
if (currentQuota > remainingQuota && fromDate != 0) {
chatBot.postMessage(debug, Instant.now() + " Quota has been reset. Was " + remainingQuota + " is now " + currentQuota);
}
remainingQuota = currentQuota;
List<StackExchangeComment> items = comments.getItems();
if (items != null) {
if (items.size() >= 100) {
chatBot.postMessage(debug, Instant.now() + " Warning: Retrieved 100 comments. Might have missed some. This is unlikely to happen");
}
long previousLastComment = lastComment;
for (StackExchangeComment comment : items) {
if (comment.getCommentId() <= previousLastComment) {
continue;
}
lastComment = Math.max(comment.getCommentId(), lastComment);
fromDate = Math.max(comment.getCreationDate(), fromDate);
if (isInterestingComment(comment)) {
chatBot.postMessage(params, comment.getLink());
}
}
}
if (comments.getBackoff() != 0) {
nextFetch = Instant.now().plusSeconds(comments.getBackoff() + 10);
chatBot.postMessage(debug, Instant.now() + " Next fetch: " + nextFetch + " because of backoff " + comments.getBackoff());
}
} catch (Exception e) {
logger.error("Error retrieving comments", e);
chatBot.postMessage(debug, Instant.now() + " Exception in comment task " + e);
return;
}
}
private boolean isInterestingComment(StackExchangeComment comment) {
String commentText = comment.getBodyMarkdown().toLowerCase();
return commentText.contains("code review") || commentText.contains("codereview");
}
WebhookParameters.java
public class WebhookParameters {
private String roomId;
private Boolean post;
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public void useDefaultRoom(String defaultRoomId) {
if (roomId == null) {
roomId = defaultRoomId;
}
}
public boolean getPost() {
return post == null ? true : post;
}
public void setPost(Boolean post) {
this.post = post;
}
public static WebhookParameters toRoom(String roomId) {
WebhookParameters params = new WebhookParameters();
params.setPost(true);
params.setRoomId(roomId);
return params;
}
}
StackExchangeAPIBean.java
public class StackExchangeAPIBean {
private final ObjectMapper mapper = new ObjectMapper();
@Autowired
private BotConfiguration config;
public StackExchangeAPIBean() {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public StackComments fetchComments(String site, long fromDate) throws JsonParseException, JsonMappingException, IOException {
final String filter = "!1zSk*x-OuqVk2k.(bS0NB";
final String apiKey = config.getStackAPIKey();
URL url = new URL("https://api.stackexchange.com/2.2/comments?page=1&pagesize=100&fromdate=" + fromDate +
"&order=desc&sort=creation&site=" + site + "&filter=" + filter + "&key=" + apiKey);
URLConnection connection = url.openConnection();
connection.setRequestProperty("Accept-Encoding", "identity");
return mapper.readValue(new GZIPInputStream(connection.getInputStream()), StackComments.class);
}
}
StackComments.java
public class StackComments {
@JsonProperty
private List<StackExchangeComment> items;
@JsonProperty("has_more")
private boolean hasMore;
@JsonProperty("quota_max")
private int quotaMax;
@JsonProperty("quota_remaining")
private int quotaRemaining;
@JsonProperty
private int backoff;
@JsonProperty("error_id")
private int errorId;
@JsonProperty("error_message")
private String errorMessage;
@JsonProperty("error_name")
private String errorName;
public int getBackoff() {
return backoff;
}
public int getErrorId() {
return errorId;
}
public String getErrorMessage() {
return errorMessage;
}
public String getErrorName() {
return errorName;
}
public List<StackExchangeComment> getItems() {
return items;
}
public int getQuotaMax() {
return quotaMax;
}
public int getQuotaRemaining() {
return quotaRemaining;
}
}
StackExchangeComment.java
public class StackExchangeComment {
@JsonProperty("post_id")
private long postId;
@JsonProperty("comment_id")
private long commentId;
@JsonProperty("creation_date")
private long creationDate;
@JsonProperty
private String body;
@JsonProperty
private String link;
@JsonProperty("body_markdown")
private String bodyMarkdown;
public String getBody() {
return body;
}
public String getBodyMarkdown() {
return bodyMarkdown;
}
public long getCommentId() {
return commentId;
}
public long getPostId() {
return postId;
}
public String getLink() {
return link;
}
public long getCreationDate() {
return creationDate;
}
}
我知道
WebhookParameters
并不是不可变的,这主要是因为它可以用作URL请求Spring MVC中的参数。 ?还有其他评论吗?
#1 楼
我在“其他任何评论”类别中有一个:)本地化?
我知道,这不是问题。还是?本地化似乎总是落伍!具有讽刺意味的是,正是由于这个原因,对应用程序进行本地化常常是一件令人头疼的事。
if (items.size() >= 100) {
chatBot.postMessage(debug, Instant.now() + " Warning: Retrieved 100 comments. Might have missed some. This is unlikely to happen");
}
像这样:
if (items.size() >= 100) {
chatBot.postMessage(debug, Instant.now() + Resources.Warn100CommentsReceived);
}
我假设Java在这里有类似c#的资源,如果我只是将一只脚放在嘴里,我深表歉意。它看起来确实更干净:)
'100'是一个魔术数字
这就是说,
items.size() >= 100
不是“收到100条评论”-该消息不能反映代码的作用,这意味着蛋糕消息很可能是谎言。而且,如果您突破了该限制,您可能想知道多少。如何从
items.size()
中提取变量,然后将其串联到消息中,而不是硬编码的100
中?在Stack Overflow活动的最后26周中,数字为:4,320,919条评论。
116,900和188,280条评论之间-每周平均166,190条。
52周的平均数量为每周177,757条评论:未来几个月可能比我们后面的评论忙。
每分钟16.5条评论,两分钟内为32.97条。
我同意
100
是可以使用的合理数字。但是...“ 100”是一个神奇的数字!虽然您在两分钟内收到30条评论,但至少有4-5个新用户加入了Stack Overflow(每周约有23K新用户)-以这种速率,最终可能需要用更高的数量代替100
。该值是完全任意的(为什么不是255?),并且显然属于private static final
字段-如果没有在应用程序设置/配置文件中,这是我希望在其中找到的值。#2 楼
就像Mat的杯子已经指出的那样,您的代码中有一些魔术数字。private final WebhookParameters params = WebhookParameters.toRoom("8595");
private final WebhookParameters debug = WebhookParameters.toRoom("20298");
摆脱它们。
您不应该对stackexchange api网址进行硬编码。而是将其放在配置文件中。如果api更改,则网址也会更改。如果更改没有破坏您的应用程序,则URL可能会中断。
也许使用
Url composeUrl()
方法是一个好主意。 恕我直言,您在
scanComments()
方法中做了很多工作。您正在从StackExchangeAPIBean
提取注释,处理剩余配额,处理注释并通过退避值计算下一次提取。应该/应该将其提取为单独的方法。 如果
chatBot.postMessage
抛出异常,则也将在catch
部分内部发生。您最好将postMessage()
方法包含在try..catch中,并引发自己的异常,然后不应通过再次调用postMessage()
来处理该异常。 useDefaultRoom()
方法有点奇怪,恕我直言。我将其重命名为setRoomIdIfNull()
,但我认为它没有任何意义(也许缺少某些上下文?)。
评论
我不会像在“有趣的注释”中寻找的字符串那样对代码进行注释。仅是代码审查||与代码审查相关的codereview?我可以想到重构和分离。我不确定您希望/期望该机器人具有多强的功能。也许添加太多“搜索词”会导致很多错误/肯定。虽然只是食物,但是好主意!@ChrisCirefice谢谢。确实可以关联,是的。但是,我们主要要抓住的是发布在CR上的建议。机器人已经发布了一些错误的肯定信息(特别是从今天起,它也为“程序员”提供帮助的同时也在做同样的事情)。
您不认为投票最多的答案应该能赢得支票吗?一年过去了。