Written by Julie Zelenski
Our exams are designed to evaluate your mastery of the course learning goals. The exam questions usually will ask you to complete coding tasks and answer thought questions. The exam questions will focus on material from the assignments, labs, lecture, and reading (this list is in order of decreasing emphasis). As an example, it is pretty much guaranteed the midterm will contain a question that requires you to manipulate raw memory using void*
as assignments 2 and 3 did nothing but that. On the other hand, we are not going to test you on some obscure fact out of the reading that was never even mentioned in lecture, lab, or an assignment (such as how to tweak the floating point rounding mode). Answering the exam questions is typically going to require good comprehension of the foundational concepts and the ability to analyze and apply them in a given situation. We are evaluating that not only did you successfully complete the assignments and labs, but that you came away with a comprehension you can demonstrate. We generally do not ask superficial questions about terminology (e.g. "Define overflow") or details that can/should be looked up on demand ("How do you printf a number in hexadecimal number padded to 8 digits?").
We will give you the previous quarter's exam as a sample. It is reasonable to expect fairly formulaic exams from me in terms of question formats and topics. This is intentional-- given the limited time allocated to exams, this is not the time to shake things up with a wacky new type of question! Many questions will ask you to write short passages of C code. Other questions may ask you to analyze C code: to trace its behavior, to compare or contrast two versions, or identify and fix flaws. On the final, we will ask you to similarly analyze assembly instructions or translate between C/assembly. There may also be short-answer thought questions that ask you to reason about system behavior, coding tasks, numeric representation and limitations, performance tradeoffs, and the like.
When scoring answers, we will not be picky about minor syntax and oversights (missing braces around a block of clearly indented code, forgetting to declare an integer variable, we don't ask for #include's etc.), but there are subtleties that matter immensely, e.g., a int**
is just one character different than int*
, yet there is a world of difference between the two! The lion's share of the points for a problem will be reserved for grading the critical core of the code, and it is worth it to be very careful with the details on those portions. For example, if we ask you to write a function that sums the elements in a CVector, only a tiny fraction of the points will be allocated to the banal chores (initializing the counter, iterating over the right bounds). The bulk of the points will be gained or lost in the tricky details of whether you correctly handles the void*
s. Being off-by-one in the loop is a tiny deduction, but being a level of indirection off or applying the wrong cast is going to be a huge hit. Be sure to focus your attention accordingly!
The kind of systems coding we do in CS107 leaves much room for error, spanning the gamut from careless oversights such as forgetting to return the function result to more serious omissions such as failing to allocate memory for a pointer. Nearly everyone loses a few points that could have been avoided with more time and chance to execute/test the code. However a solid first-pass approximation that shows the correct conceptual understanding of the key issues will earn the bulk of the points and, in our analysis, deserves to be strongly distinguished from code that exhibits significant conceptual issues and would require much trial-and-error with compiler/debugger/valgrind to turn into working code.
For most students, the assignment and exam performance are fairly well correlated but sometimes they do diverge a bit. It's pretty rare to rock the exam if your assignments didn't go well, but there are students who go into the exam with solid assignment scores and yet emerge with a disappointing outcome. What might explain this and what can you do about it?
The untold hours you invest in your assignments are hopefully buffing out your coding chops to make you invincible. If you come into the exam with a rock-solid command of pointers and memory, you are absolutely ready to crush the exam. However, it is possible to have successfully completed the assignments without mastering the material. We see students that "debug code into existence", use trial and error experimentation, have deep dependence on tools, require continual access to the course staff, brute force through permutations of * & and typecasts, and so on. If this is you, you want to rethink your strategy. By dint of effort, you may end up with a working CMap and an excellent score, but if the methods don't lead to comprehension, you won't be able to reproduce these results and will have a very difficult time with exams. It's not that you aren't working hard-- far from it, you may be investing more time in the assignments than most, but you need to adjust your strategy to get more out of the time you are spending. The goal of an assignment is not to get the program to work, one way or another, it is about developing an understanding how and why it works, and being able to take that understanding and write similar code in any environment, including one that doesn't have the luxury of a compiler/debugger/Valgrind/TA/sanitycheck to tell your whether the code is correct or not.
You may be on top of most of the material, but some issues remain hazy. There is a difference between a "pretty good" understanding and having the concepts completely dialed. If you're spending a lot of exam time pawing through your materials and puzzling through the concepts, it suggests that there is an opportunity to fortify your foundation beforehand. Or consider: are the errors in your answers borne out of carelessness or indicative of more significant knowledge gaps? It takes courage to be honest with yourself, it's tempting to label every mistake as an oversight: "I just forgot to use char**
here instead of char*
". Being a level of indirection off may be syntactically small but is conceptually enormous. The fully-formed scar tissue from completely wrestling pointers into submission is a powerful reminder to never make those mistakes again. A more iffy understanding can crack under pressure. Use the exam results to find where your expertise is lacking so you know where you need to shore it up.
The exam results could mean you're not that comfortable in the testing environment. You crank out great assignments, you thoroughly understand the material, but in the exam you get flustered, mis-read the instructions, budget your time poorly, get sloppy, or all of the above. This effects may be overcome by more practice in test-like conditions (longhand, on paper, no tools, time limits). Working on your test-taking skills won't do much for your real-world competence (as it is already good), but it could improve your course grade and these skills may also be of use in interviews. If you're sure that it's just testing prowess that you're lacking, I recommend an approach in moderation -- review your exam, introspect on what happened, brainstorm tactics for next time, do some practice -- but don't let it make you crazy. If you're confident about what you are learning, can you make your peace with the nature of the artifact captured on an exam and let it go? Lucky for us, CS as a field is an incredible meritocracy. If you have good ideas, know how to solve problems, are willing to work hard, can meet deadlines, and communicate effectively, you'll have people begging you to work for them, regardless of the impressiveness of your pedigree/transcript. One of the best colleagues I've had was a dropout from Berkeley (a fact I never let him forget :-) He is a coding ninja, architectural genius, inspiring leader, and all-around nice guy who I'd follow anywhere. Did I mention he dropped out from Cal? I admire someone who couldn't even hack it at Cal? Sheesh!
Crushing a CS107 exam is a sure sign that you are on top of your game. Potential employers are usually impressed by a good performance in CS107, as it bodes well for your ability to write solid code to solve problems.
Does proficiency in mentally evaluating code actually help with real-world coding projects? A subset of systems programming is programming for embedded systems (including mobile) where it's very often the case that debugging tools are non-existent. In these scenarios it's very helpful to be able to quickly figure out what a piece of code is doing without having to rely on gdb, valgrind, or even printf() since these tools may be missing entirely.
More broadly, the ability to analyze and reason about code in isolation is a valuable skill for any programmer. Investing the time to understand the code you write and strive for sincere mastery means you are on your way to being a coding rockstar. A former CS107 student put it this way: " Think 10 years down the road to when you're developing a product and your coworker offers you some code that you can't understand. Testing the code seems to show to work, but you have no idea why. You ask your coworker and he can't explain it either but insists that it works, so just use it. Do you have faith in that code? In your coworker?" You want to be the one that people clamor to work with, not just because you contribute solid code, but because you can clearly communicate how and why your code works!