-
-
Notifications
You must be signed in to change notification settings - Fork 361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor download cmd - compose & extract shared behavior for new doctor cmd #756
Refactor download cmd - compose & extract shared behavior for new doctor cmd #756
Conversation
46761cc
to
cfcf8e9
Compare
80c9ff4
to
20851be
Compare
1444385
to
1b83874
Compare
Questionable points:
|
01dfa38
to
d409dae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a really nice improvement ✨
I have a few suggestions/questions. Would love to hear your thoughts!
@@ -79,191 +69,31 @@ func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error { | |||
return err | |||
} | |||
|
|||
param := "latest" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels so good to get all this logic out of here! 😍
cmd/download_helper.go
Outdated
@@ -0,0 +1,244 @@ | |||
package cmd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right that download_helper.go
feels a bit odd here. I wonder if we could just stick all the shared logic into cmd/cmd.go
so that all files in the rest of this package are either command files or test files.
The other option is to stick this logic somewhere else (different package) but that feels like a much bigger hammer, and I'd like to wait before we go down that route.
6455cc8
to
deded6c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments/suggestions. I really love where this is going!
cmd/cmd.go
Outdated
payload *downloadPayload | ||
} | ||
|
||
func newDownload(ctx *downloadContext) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure newDownload
is the right name here. Generally if I see a function newDownload
I would expect it to return a Download
value or a pointer to a download value. In this case it performs a download, right?
cmd/cmd.go
Outdated
} | ||
|
||
query := req.URL.Query() | ||
ctx.buildQuery(query) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the buildQuery
initialize the query itself as well? Something like:
query := ctx.buildQuery()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a better way of doing it. Initializing requires a reference to the *http.Request
, right? I don't see how we can avoid having to pass that in.
Edit: Actually, there is a better way of doing it (though it still requires passing in a reference to the request).
cmd/cmd.go
Outdated
return nil | ||
} | ||
|
||
func (d *downloadContext) writeMetadata() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes we've called this d
other times we've called it ctx
. I think it would be worth being consistent.
cmd/cmd.go
Outdated
return nil | ||
} | ||
|
||
func (d *downloadContext) getExercise() workspace.Exercise { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style-wise in the CLI we typically don't use get
prefixes for getters, we'll usually name the method after what it returns (so exercise()
).
cmd/cmd.go
Outdated
} | ||
} | ||
|
||
func (d *downloadContext) getMetadata() workspace.ExerciseMetadata { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here, this should probably be metadata()
.
abbd257
to
75218ba
Compare
Thanks for the feedback. Do we want to make any changes to the tests? I think we might be forced into making a move since there will be shared behavior behavior on I think it's also worth noting (though might not directly affect this PR) that there are a few other methods that live in |
Yeah, I think that would be a good move. It means that if we don't find the helper method in the test file we're in, we know to look for it there. |
f99d7b5
to
29cf184
Compare
c934fc6
to
738f5f5
Compare
Based on exercism#794, we've decide to error out and display API error messages. This overlaps with cmd/submit, so this change extracts it for sharing.
738f5f5
to
f87b49c
Compare
The body is small enough that it doesn't seem worth the cost of extracting.
This has been sitting for awhile and I don't plan on making further changes. This message will serve as a status of present concerns: I don't think If we move it to a new file in the Unrelated: I had previously expressed concerns about testing but I don't think it's worth worrying about. |
Thanks @jdsutherland. I'm going to close this, but it has been a really useful discussion. The download command is a bit of a monster and I honestly don't quite know where to go with it. |
I'm a bit surprised by the jump to closing this without discussion and I don't really have a sense as to why. In the interest of feedback, it'd be useful to understand the thought process here - was this just so far off the mark that it wasn't worth iterating on? |
@jdsutherland You said you weren't going to work on it anymore. Did I misunderstand? |
@kytrinyx I see how what I said could be misunderstood. The previous message was just an attempt to give an up to date status - I had posted several comments in the past that I now deem outdated based on iterating on this so I was trying to say: this seems stable and I don't plan on changing this until I get further feedback. Sorry for the confusion. |
Oh my goodness I'm so sorry. I totally see how that can be read both ways, now that I know what you intended! So. Since I thought you had given up on this one, I basically took the direction that I liked about it, and extracted a bunch of little bits and pieces into separate PRs (I used the The main idea is based on what you did here, but it has fewer abstractions. Would you pull that down and see if it would solve the "extract so we can re-use" problem for you? |
No worries. I'm not sure if it's best to put things here or in #820, but I'll start here because this PR is relevant in reference to what I'm saying (I'll move it if we decide otherwise). I think this PR (#756) may be overly abstract or complex than is needed but I think we're going to want more pulled out than is currently in #820. I envision the part of doctor that migrates legacy metadata will look like this: exercises, err := ws.PotentialExercises()
if err != nil {
return err
}
for _, exercise := range exercises {
if _, err := exercise.MigrateLegacyMetadataFile(); err != nil {
return err
}
if ok, _ := exercise.HasMetadata(); !ok {
download, err := newDownloadFromExercise(exercise, cfg.UserViperConfig)
if err != nil {
return err
}
writer, err := newDownloadWriter(download)
if err != nil {
return err
}
if err := writer.writeMetadata(); err != nil {
return err
}
}
} If you think about implementing this from #820, there's a lot of potential duplication among shared behavior.
It doesn't seem that really any of the behavior is going to differ between them so it seems appropriate to abstract it out and encapsulate it. I think there's good potential future reasons for pulling it out as well (#718 is one example). |
Yepp, that makes perfect sense to me. I think I'd prefer to do the extractions in multiple PRs, though, rather than in one big one, since it is so much harder to review a big PR (which means I end up putting it off much longer). Shall we see if we can get #820 into a reasonable state for what it's currently doing, and then tackle the next extractions in a series of smaller PRs, discussing how far to take things each time? |
Yeah, I started this not really knowing where it would go and it became quite large; breaking it into smaller PRs makes sense. I'm not sure how I feel about moving forward with #820. I've put a good amount of work into this PR and it seems to facilitate what's needed for the doctor command. I'm happy to adjust things based on review but throwing most of it out without feedback and starting over from another point doesn't make much sense to me. If there's a good reason for doing so, I'm interested to understand why but I currently don't see it. |
Ok, sure. Let's move forward with this one. I'll close #820. However, would you be willing to break it up into smaller pieces, one at a time? It's really challenging to review a change that is this big. If you have an idea of where you're going, could we take it in smaller steps? |
Sure. Given how many garbage commits this has (and it has conflicts that need resolved anyway), it might make more sense to start from master. Since that I can't unsee this, it's probably going to be hard to me to gauge what comprehensible steps look like. Should we proceed by me guessing and go from there or should we try to clear certain things up first? For example, is it possible to look at it now and say that there are concerns about the main abstractions? |
Yeah, I know the feeling :)
I think that makes sense. My main concern is that there are so many abstractions I think that having fewer abstractions, at least to start, would make more sense. Let me go through the PR and see if I can suggest some first steps. |
Ok, what do you think about starting off with a very simple In this first step, I'd like to avoid thinking about what the |
Looking at master and at your code again, I think that we'll end up pulling all the flag stuff out of |
#821 is an attempt at following what you've said. My concern is that this can seem bad in the context of intermediate steps but in the finished state, it makes more sense. It's possible that the types I have aren't necessary but I think that download may end up being large and having multiple responsibilities whereas I was viewing it as a simple value object that just holds data. |
This is going in a different direction so it seems fit to close this. |
These changes are motivated by #744, in which the new
doctor
command needs to download missing metadata for exercises in the workspace.cmd/download
contained the logic for this but to be usable it needed refactoring; due to the overlapping need for downloading metadata, it makes sense to extract it outside of an individual command and split the download logic into separate functions.